应使用 object.prototype.hasownproperty.call(obj, key) 检查属性存在性,先通过 obj != null 确保对象合法,再统一支持字符串与 symbol 键,避免触发 getter 或依赖枚举性。

直接检查属性是否存在,不能只靠 obj.prop !== undefined 或 'prop' in obj,因为它们在原型链、null/undefined、getter 报错、Symbol 属性等场景下容易出错。健壮的检查需兼顾安全性、准确性与通用性。
先做类型安全判断
任何属性访问前,必须确认目标是合法对象(排除 null 和 undefined),否则会抛出 TypeError。用 obj != null(即非 null 且非 undefined)快速过滤原始值和空值:
-
if (obj == null) return false;—— 这是第一道防线 - 注意不要用
typeof obj === 'object',它会把null也判为 true - 数组、Date、RegExp、Map 等都属于 object 类型,无需额外排除,它们都支持属性访问语义
区分自有属性与继承属性
多数业务场景关心的是对象“自己定义”的属性,而非从原型继承来的。应优先使用 Object.prototype.hasOwnProperty.call(obj, key):
- 比
obj.hasOwnProperty(key)更安全——避免对象自身重写了hasOwnProperty - 不触发 getter,不会因副作用或异常中断执行
- 对 Symbol 键同样有效:
hasOwnProperty.call(obj, Symbol('x'))
支持字符串与 Symbol 两种键类型
ES6+ 中属性名可以是字符串或 Symbol。健壮函数需统一处理两者:
- 接受任意类型的
key参数,内部自动识别:typeof key === 'symbol' - 避免强制转字符串(如
String(key)),否则Symbol('a')和Symbol('b')都变成"Symbol(a)"和"Symbol(b)",但它们仍是不同键 - 对非字符串非 Symbol 的 key(如数字、布尔),按 JS 规则隐式转为字符串(
obj[1]等价于obj['1']),可保留该行为,无需特殊拦截
不触发 getter,也不依赖枚举性
有些属性是不可枚举的(如 Object.defineProperty(obj, 'x', { value: 1, enumerable: false })),或有副作用的 getter。健壮检查应绕过这些风险:
- 不用
Object.keys()或for...in,它们只遍历可枚举属性 - 不用
obj[key] !== undefined,可能触发 getter 并抛错 -
hasOwnProperty是唯一标准方式:它只查属性存在性,不读值、不执行代码、不依赖枚举标志











