for…in、Object.keys、Reflect.ownKeys、in 和自有属性判断有什么区别?

先理解原理和示例,再练习完整口述与追问。

区分自有/继承、可枚举/不可枚举、字符串/Symbol;不要用值是否 undefined 判断存在;数组遍历要区分索引和值。

for…in 遍历对象自身及原型链上可枚举的字符串键,不包含 Symbol,因此处理纯对象时通常还需过滤继承属性。Object.keys 只返回自身可枚举的字符串键,Object.values 和 Object.entries 采用相同属性范围。Object.getOwnPropertyNames 包含自身所有字符串键,包括不可枚举属性;Object.getOwnPropertySymbols 返回自身 Symbol 键;Reflect.ownKeys 同时返回自身字符串和 Symbol 键,不要求可枚举。

key in obj 判断自身或原型链上是否存在这个属性;Object.prototype.hasOwnProperty.call(obj, key) 只判断自身属性,并适用于无原型对象或覆盖了 hasOwnProperty 的对象。Object.hasOwn 更简洁,但 Node 16.19 等较旧环境应先确认支持或使用前一种兼容写法。obj[key] !== undefined 不能判断属性存在,因为真实存在的属性也可以存 undefined。

数组读取元素通常使用 for…of 或数组方法,for…in 可能扫到额外可枚举属性和继承属性。稀疏数组也是边界:for…of 按位置取值,空位表现为 undefined;Object.keys 只列出存在的自身键,map 等方法则跳过不存在的索引。

设 obj 继承 {inherited:1},自身有 enumerable 键 name、不可枚举键 hidden 和一个 Symbol 键。Object.keys 只列 name;for…in 还可能列 inherited;Reflect.ownKeys 列自身三种键。obj.missing 与 obj.defined 都可能读到 undefined,但只有后者真实存在。

若读取属性触发 getter,枚举键与实际读值也是不同操作。处理外部对象还要考虑原型污染,不把任意键直接写到敏感原型。

for…in 遍历对象自身及原型链上可枚举的字符串键,不包含 Symbol,因此处理纯对象时通常还需过滤继承属性。Object.keys 只返回自身可枚举的字符串键,Object.values 和 Object.entries 采用相同属性范围。Object.getOwnPropertyNames 包含自身所有字符串键,包括不可枚举属性;Object.getOwnPropertySymbols 返回自身 Symbol 键;Reflect.ownKeys 同时返回自身字符串和 Symbol 键,不要求可枚举。