- 教程
【免费下载链接】jstips
This is about useful JS tips!
导读
在 JavaScript 日常开发中,判断一个对象是否拥有某个属性是最高频的操作之一。本文以 jstips 仓库 Check if a property is in a Object(第 10 期技巧)为骨架,系统讲解if (obj.prop)直接判断的陷阱、in操作符与Object.prototype.hasOwnProperty两种原生方案的本质差异(关键在“检查深度”:自身属性 vs 原型链继承属性),并结合现代 ES2022 的Object.hasOwn给出兼顾安全与兼容的实战建议。读完你将能准确选择属性存在性检查方案,避免原型链污染与隐式类型转换带来的隐蔽 Bug。
一、最常见的做法:if (myObject.name)为什么不够严谨
原文档首先指出,很多开发者判断属性是否存在时习惯这样写:
var myObject = { name: '@tips_js' }; if (myObject.name) { ... }这段代码在name属性存在且值为真值时能正常工作,但它存在两个致命缺陷:
- 真值性(truthy)检查不等于存在性检查。
if (myObject.name)实际做的是“myObject.name是否为真值”,而不是“myObject上是否定义了name”。当属性的值恰好是''、0、false、null、undefined或NaN这类假值(falsy)时,即使属性真实存在,条件也会判定为不成立。例如var o = { count: 0 }; if (o.count) { ... }永远不会进入分支,但count属性确实存在。 - 无法区分“属性不存在”与“属性存在但值为假”。很多业务 Bug 正源于此:默认值覆盖、条件渲染、配置项合并等场景中,开发者需要精确区分这两种情况。
因此,原文档推荐使用两种原生方法:in操作符与Object.hasOwnProperty。它们都来自Object原型链,任何继承自Object的对象都可以直接调用。
二、两种原生方案:in操作符
in操作符用于判断某个属性是否存在于对象自身或其原型链上,语法为:
prop in object其中prop是字符串或 Symbol 类型的属性名,object是需要检查的对象。如果属性存在于对象自身或沿原型链向上可达,返回true,否则返回false。
var myObject = { name: '@tips_js' }; 'name' in myObject; // true 'age' in myObject; // false三、两种原生方案:Object.prototype.hasOwnProperty
hasOwnProperty是Object.prototype上的方法,它只检查属性是否为对象自身的直接属性(own property),完全不考虑原型链:
myObject.hasOwnProperty('name'); // true注意,它是原型链上的方法,因此几乎所有对象(除Object.create(null)创建的空原型对象外)都能直接调用。如果对象是通过Object.create(null)创建的,它没有任何原型方法,直接调用obj.hasOwnProperty(...)会报错,此时应改用Object.prototype.hasOwnProperty.call(obj, key)或 ES2022 的Object.hasOwn。
四、核心差异:检查深度不同(原文档核心示例)
原文档用一个对比示例展示了两种方案的“深度”差异:
var myObject = { name: '@tips_js' }; myObject.hasOwnProperty('name'); // true 'name' in myObject; // true myObject.hasOwnProperty('valueOf'); // false, valueOf 继承自原型链 'valueOf' in myObject; // true结论一目了然:
hasOwnProperty只在该属性是对象自身直接属性时返回true;in操作符不区分属性是对象自身创建的还是从原型链继承来的,只要链上能找到就返回true。
valueOf、toString、constructor、hasOwnProperty本身等都属于Object.prototype上的继承属性,所以'valueOf' in myObject为true,而myObject.hasOwnProperty('valueOf')为false。
构造函数的原型链示例
原文档进一步用构造函数验证这一差异:
var myFunc = function() { this.name = '@tips_js'; }; myFunc.prototype.age = '10 days'; var user = new myFunc(); user.hasOwnProperty('name'); // true user.hasOwnProperty('age'); // false, 因为 age 来自原型链这里name是构造函数体内通过this.name = ...创建的实例自有属性,而age挂在myFunc.prototype上,因此:
'name' in user; // true(自身属性) 'age' in user; // true(原型链上可达) user.hasOwnProperty('age'); // false(不是自身属性)这正是“检查深度不同”的完整含义:in覆盖整条原型链,hasOwnProperty只覆盖对象自身。
五、实战误区与原型链“假阳性”
理解上述差异后,几个经典误区需要特别警惕:
for...in遍历与hasOwnProperty的经典搭配。for...in会遍历包括继承属性在内的所有可枚举属性,因此老代码中常见if (obj.hasOwnProperty(key))过滤。仓库另一篇技巧 Hash maps without side effects 正是针对此问题:使用Object.create(null)创建无原型对象,遍历时便无需再做自身属性检查,也彻底避免in/for...in被原型链污染的问题。- 属性名恰好覆盖原型方法。若对象自身定义了名为
hasOwnProperty的属性(例如数据中恰好有该 key),直接调用obj.hasOwnProperty(key)会得到非函数而抛错,这也是原生方法被“屏蔽”的经典坑。 - 用
in判断“安全存在”并不安全。in只回答“链上是否存在”,不回答“是否属于你”,在序列化、深拷贝、白名单过滤等场景,若混入继承属性会导致数据污染。
仓库 Picking and rejecting object properties 中的pick实现使用k in obj判断后取值,正是因为pick业务上希望容忍原型链属性;而reject实现改用Object.keys(obj)仅枚举自身可枚举属性,两种工具对“深度”的不同取舍恰与本文主题互为印证。
六、现代演进:Object.hasOwn与更多替代方案
针对hasOwnProperty被覆盖、以及Object.create(null)对象无该方法的问题,ES2022 引入了静态方法Object.hasOwn(obj, prop):
Object.hasOwn(myObject, 'name'); // true Object.hasOwn(myObject, 'valueOf'); // false它等价于Object.prototype.hasOwnProperty.call(obj, prop),且对Object.create(null)对象同样安全。此外还有几种常用方案:
Object.keys(obj):返回自身可枚举属性名数组,可用includes判断;缺点是不可枚举属性与 Symbol 属性不会出现。Object.getOwnPropertyNames(obj):返回自身所有字符串属性名(含不可枚举),不含 Symbol。Reflect.ownKeys(obj):返回自身所有属性键(含不可枚举与 Symbol)。
选择建议:
| 需求 | 推荐方案 |
|---|---|
| 只关心对象自身属性(含不可枚举),ES2022 环境 | Object.hasOwn(obj, key) |
| 需要兼容 ES5/旧环境 | Object.prototype.hasOwnProperty.call(obj, key) |
| 需要沿原型链判断属性可达性 | key in obj |
| 需要“值存在且非假值”的业务判断 | if (obj[key])(明确知道语义前提) |
| 需要遍历自身可枚举属性 | Object.keys(obj) |
七、小结:按语义选择检查方式
回到原文档的核心结论:判断属性是否存在,不应依赖真值判断,而应在“自身属性”与“原型链可达属性”两个语义之间做出明确选择——前者用hasOwnProperty/Object.hasOwn,后者用in。原文档最后还建议读者参考社区关于“检查属性存在时的常见错误”的讨论(见仓库对应 issue 记录)。在 jstips 仓库中,本文对应的中文翻译见 _posts/zh_CN/javascript/2016-01-10-check-if-a-property-is-in-a-object.md,繁体版见 _posts/zh_TW/javascript/2016-01-10-check-if-a-property-is-in-a-object.md,西班牙语版见 _posts/es_ES/javascript/2016-01-10-check-if-a-property-is-in-a-object.md,可供对照阅读。相关延伸还可参考仓库 Advanced Properties(属性描述符与 getter/setter)、Why you should use Object.is() in equality comparison-in-equality-comparison.md)(相等性比较)与 Short circuit evaluation in JS(短路求值),它们共同构成 JavaScript 对象操作中“精确判断”的完整工具箱。
- 教程
【免费下载链接】jstips
This is about useful JS tips!
相关推荐
Vuetify 无障碍(a11y)实践指南:Activator 语义属性与键盘焦点管理
Vuetify 无障碍(a11y)实践指南:Activator 语义属性与键盘焦点管理 Web 无障碍(Accessibility,简称 a11y)是一项包容性
教程freeCodeCamp Basic JavaScript 挑战精讲:用 hasOwnProperty() 检测对象的属性是否存在
freeCodeCamp Basic JavaScript 挑战精讲:用 hasOwnProperty 检测对象的属性是否存在 本文基于 freeCodeCam
前端后端教育千古前端图文教程:JavaScript 对象的高级操作——hasOwnProperty 属性检测与 Object.freeze 对象冻结
千古前端图文教程:JavaScript 对象的高级操作——hasOwnProperty 属性检测与 Object.freeze 对象冻结 本文是《04 Java
文档教程前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考