☰
jstips 第 10 期:检查对象属性是否存在的正确姿势 —— `in` 操作符与 `hasOwnProperty` 深度对比
2026/10/8 18:22:25 网站建设 项目流程
  • 教程

【免费下载链接】jstips

This is about useful JS tips!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载

导读

在 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属性存在且值为真值时能正常工作,但它存在两个致命缺陷:

  1. 真值性(truthy)检查不等于存在性检查。if (myObject.name)实际做的是“myObject.name是否为真值”,而不是“myObject上是否定义了name”。当属性的值恰好是''、0、false、null、undefined或NaN这类假值(falsy)时,即使属性真实存在,条件也会判定为不成立。例如var o = { count: 0 }; if (o.count) { ... }永远不会进入分支,但count属性确实存在。
  2. 无法区分“属性不存在”与“属性存在但值为假”。很多业务 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只覆盖对象自身。

五、实战误区与原型链“假阳性”

理解上述差异后,几个经典误区需要特别警惕:

  1. for...in遍历与hasOwnProperty的经典搭配。for...in会遍历包括继承属性在内的所有可枚举属性,因此老代码中常见if (obj.hasOwnProperty(key))过滤。仓库另一篇技巧 Hash maps without side effects 正是针对此问题:使用Object.create(null)创建无原型对象,遍历时便无需再做自身属性检查,也彻底避免in/for...in被原型链污染的问题。
  2. 属性名恰好覆盖原型方法。若对象自身定义了名为hasOwnProperty的属性(例如数据中恰好有该 key),直接调用obj.hasOwnProperty(key)会得到非函数而抛错,这也是原生方法被“屏蔽”的经典坑。
  3. 用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!

项目地址:https://gitcode.com/gh_mirrors/js/jstips
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询