先记住这个答案
in 和 hasOwnProperty 的区别在查找范围。'toString' in {} 是 true,因为 in 会沿原型链找到 Object.prototype.toString;({}).hasOwnProperty('toString') 是 false,因为它只看对象自己。判断自有属性,现在推荐用 ES2022 加的 Object.hasOwn(obj, key),它不依赖对象身上的方法,遇到没有原型的对象也不会出错。
in查整条原型链,hasOwn只查自身- 两者判断的都是键在不在,不管值是不是
undefined - 无原型对象没有
hasOwnProperty for...in会遍历继承键
同一个对象,两种判断结果不同
拿一个有原型的对象试一下,区别一眼就能看出来。Object.create(proto) 创建的对象会把 proto 当作原型。
undefined 的属性JavaScriptconst proto = { shared: 1 }
const obj = Object.create(proto) // obj 的原型是 proto
obj.own = 2
obj.empty = undefined
console.log('shared' in obj) // 原型上有,true
console.log(obj.hasOwnProperty('shared')) // 自己没有,false
console.log(Object.hasOwn(obj, 'own')) // 自己有,true
console.log('empty' in obj, obj.empty !== undefined)查看输出与解释
true
false
true
true falseshared 定义在原型上,in 能看到,hasOwnProperty 看不到。最后一行说明另一件事:in 和 hasOwn 判断的是「这个键存不存在」,和值是什么无关。属性值是 undefined 时,用 obj.empty !== undefined 会误判成不存在,用 in 或 hasOwn 才准。
所以选哪个取决于你要回答的问题。想知道「读 obj.x 能不能读到东西」,用 in;想知道「这个值是不是这个对象自己存的」,用 Object.hasOwn。
为什么推荐 Object.hasOwn
hasOwnProperty 本身不是语言关键字,它只是 Object.prototype 上的一个方法。对象能调用它,靠的也是原型链。这就带来两个问题。
- 没有原型的对象调不了
Object.create(null)常用来做纯净的字典,这种对象没有原型,身上什么方法都没有。写dict.hasOwnProperty('a')会直接抛TypeError,提示hasOwnProperty不是一个函数。从 JSON 解析出来的对象不会这样,但一些库和框架内部的映射表会用这种对象。- 方法可能被覆盖
- 对象自己如果有一个叫
hasOwnProperty的键,比如后端返回的数据里恰好有这个字段,调用时拿到的就是那个值,不是原生方法。以前的稳妥写法是借用原型上的方法:Object.prototype.hasOwnProperty.call(obj, key)。Object.hasOwn(obj, key)做的就是这件事,写起来更短,ESLint 的prefer-object-has-own规则也会建议替换。
遍历时怎么排除继承属性
for...in 会把原型链上可枚举的属性也遍历出来。比如用 Object.create(defaults) 做了一个配置对象,defaults 里的字段都会出现在 for...in 里。老代码里常见的写法是在循环里加一句 if (!Object.hasOwn(obj, key)) continue。
更简单的做法是直接换成 Object.keys(obj),它只返回自身可枚举的字符串键,不用再过滤。要包含不可枚举的键,用 Object.getOwnPropertyNames;要连 Symbol 键也拿到,用 Reflect.ownKeys。这几个都只看自身,不碰原型。
容易答错的地方
- 用
in判断对象自己有没有某个属性 'toString' in obj对几乎所有普通对象都是true,所以in不能用来做「自有属性」判断。比如用对象当计数表,'constructor' in counts会返回true,逻辑就出错了。- 用
obj.key !== undefined代替存在性判断 - 属性存在但值是
undefined时,这种写法会判断成不存在;属性在原型上时,它又会判断成存在。它判断的是「读出来的值」,不是「键在哪」。 - 认为
hasOwnProperty在任何对象上都能调 - 没有原型的对象、覆盖了同名键的对象都会让它失效。不确定对象来源时直接用
Object.hasOwn。
面试官还会怎么问?
不可枚举的自有属性,hasOwn 会返回什么?
返回 true。hasOwn 只管键是不是自己的,不管 enumerable。比如数组的 length 不可枚举,Object.hasOwn([], 'length') 是 true,但 Object.keys([]) 里没有它。
Object.hasOwn 不支持的老环境怎么办?
用 Object.prototype.hasOwnProperty.call(obj, key),效果一样。也可以通过 core-js 之类的 polyfill 补上 Object.hasOwn。
想知道属性具体在原型链的哪一层,怎么找?
从对象本身开始,每层用 Object.hasOwn 检查一次,没有就用 Object.getPrototypeOf 取上一层原型继续找,直到原型是 null。in 只能告诉你有没有,给不出层级。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。