如何创建有序对象?
问题
在不把对象改成数组的情况下,给对象添加新的属性,怎么保证这个属性在遍历的时候是最后一个?
解答
以前 JavaScript 中对象的键名是无序的,后来在 ES2015
之后规定了键名的顺序。
键名数组分为三个部分:
- 可作为数组索引的键名(如 0, 1, 2),升序排列。
- 字符串索引,按创建顺序排列。
- Symbol 索引,按创建顺序排列。
如果只使用第二类字符串索引,那么默认的顺序就满足需求了。而如果要加上第一类索引,那就需要自己维护键名的数组了,在新增和删除键名时对数组进行操作。
这个场景 Proxy
就再合适不过了。下面的例子是按键名创建顺序排序的对象,如有其他排列需求只需对 ownKeys
做文章即可。
测试用例
1 | const obj = proxy({ b: 1, d: 1 }) |
代码
1 | function proxy(target) { |
defineProperty
: 在目标上无论新增还是修改键名/键值,都会触发。deleteProperty
: 在目标上移除键名时,会触发。ownKeys
: 获取键名列表时,返回keys
。
要点说明
defineProperty / set
handler.set
无法拦截Object.defineProperty
与Object.defineProperties
。handler.set
会拦截以代理目标为原型的对象的set
操作。
故选择了 hanlder.defineProperty
。
ownKeys
该拦截器可以拦截以下操作:
Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
所以我们的 keys
应为上述四者的集合,
Object.keys()
是 Object.OwnPropertyNames()
的子集,
Reflect.ownKeys()
等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))
。
故在初始化时,应用 Reflect.ownKeys()
创建初始的 keys
。
另外该拦截器还存在约束,使用前请务必了解一下: handler.ownKeys 的约束 | MDN。