先记住这个答案
Reflect.ownKeys(obj) 返回对象自身的全部键,包括不可枚举的键和 Symbol 键,等于 Object.getOwnPropertyNames 和 Object.getOwnPropertySymbols 两个结果拼在一起。Object.keys 范围最小,只返回可枚举的字符串键。顺序规则两者一样:先是整数形式的键按数值升序,再是其他字符串键按添加顺序,最后是 Symbol 键按添加顺序。
Reflect.ownKeys包含符号键Object.keys只要可枚举的字符串键- 顺序:整数键升序,字符串按插入,Symbol 最后
- 都只看自身,不会列出原型上的键
一个对象,两种结果
把整数键、普通字符串键、Symbol 键和不可枚举属性混在一起,差别就很清楚了。
const sym = Symbol('id')
const obj = { b: 1, 2: 'two', a: 2, [sym]: 3, 1: 'one' }
// 加一个不可枚举属性
Object.defineProperty(obj, 'hidden', { value: 4, enumerable: false })
console.log(Object.keys(obj).join(' '))
// Symbol 不能直接拼字符串,先转成字符串
console.log(Reflect.ownKeys(obj).map(String).join(' '))查看输出与解释
1 2 b a
1 2 b a hidden Symbol(id)写的时候是 b、2、a、sym、1 的顺序,输出却是 1 和 2 排最前。Object.keys 里没有 hidden 和 Symbol 键;Reflect.ownKeys 全都有,而且 Symbol 键排在所有字符串键后面,哪怕它比 hidden 先定义。
键的顺序是怎么定的
从 ES2015 起,规范规定了普通对象自有键的顺序,ES2020 又把 for...in、Object.keys 这些遍历方法的顺序也统一了。规则分三段。
- 整数键排最前
- 能当数组下标的键,比如
'0'、'1'、'42',会按数值从小到大排在最前面。数组能按下标顺序遍历,靠的就是这条。但'01'、'-1'、'1.5'不算,它们是普通字符串,按添加顺序排。 - 字符串键按添加顺序,Symbol 最后
- 其余字符串键按第一次添加的顺序排列,删掉再重新加,会排到末尾。Symbol 键单独排在所有字符串键之后,内部也按添加顺序。
Object.keys、JSON.stringify、Object.entries都遵守同样的字符串键顺序,只是它们不管 Symbol。
实际代码里什么时候用 Reflect.ownKeys
平时遍历业务数据用 Object.keys 或 Object.entries 就够了,不可枚举属性和 Symbol 通常是内部实现细节,不应该被业务逻辑看到。
需要「一个都不能漏」的场景才用 Reflect.ownKeys。比如手写深拷贝时,想把 Symbol 键也复制过去;比如深度冻结,要把每个属性都处理到;再比如 Proxy 的 ownKeys 陷阱里,一般 return Reflect.ownKeys(target) 作为默认行为,再在此基础上过滤。另外 Reflect.ownKeys 的参数必须是对象,传原始值会抛 TypeError;Object.keys('ab') 却会先把字符串包装成对象,返回 ['0', '1']。
容易答错的地方
- 以为对象键完全按写的顺序排
- 整数键永远会被提到前面。用
{ 100: ..., 3: ..., 20: ... }存按热度排好的数据,遍历时会变成 3、20、100。需要保持插入顺序时,用Map或者数组。 - 以为
Object.keys会返回 Symbol 键 Object.keys、for...in、JSON.stringify都会跳过 Symbol 键。这也是很多库用 Symbol 做「隐藏」字段的原因,但它不是真正的私有,Reflect.ownKeys或Object.getOwnPropertySymbols都能拿到。- 把
Reflect.ownKeys当成包含原型链的完整列表 - 名字里的 own 就说明它只看自身。原型上的方法,比如
class里定义的方法,要对原型对象调用才拿得到。
面试官还会怎么问?
for...in 和 Object.keys 的结果有什么不同?
for...in 除了自身可枚举的字符串键,还会遍历原型链上可枚举的字符串键。两者都不包括 Symbol 键。对自身键来说,它们的顺序是一致的。
怎么只拿到对象的 Symbol 键?
用 Object.getOwnPropertySymbols(obj)。它返回自身所有 Symbol 键,不区分是否可枚举。
用 Proxy 的 ownKeys 陷阱能随便返回键吗?
不能。返回值必须是字符串和 Symbol 组成的数组,不能有重复;目标对象上不可配置的键必须出现在结果里;目标对象不可扩展时,结果必须和目标的自有键完全一致。违反这些约束,引擎会抛 TypeError。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。