技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTJS

5 种 JS 判断对象属性是否存在的方案

所属馆藏
JavaScript
文件格式
Markdown
原始路径
JavaScript/07-5种JS判断对象属性是否存在的方案!
本文目录10 个章节
  1. 背景:先说清楚“存在”是什么意思
  2. 1. in 运算符:检查自身属性和继承属性
  3. 2. Reflect.has():in 的函数形式
  4. 3. hasOwnProperty():传统的自身属性检查
  5. 4. Object.prototype.hasOwnProperty.call():兼容旧环境的安全写法
  6. 5. Object.hasOwn():现代的自身属性检查
  7. 对比表
  8. 实际选择建议
  9. 结论
  10. 参考资料

5 种 JS 判断对象属性是否存在的方案

Category(分类): JavaScript Status: 已更新

原文作者:程序员半夏

历史文章:5 种 JS 判断对象属性是否存在的方案

原文整理了 inReflect.has()hasOwnProperty()Object.prototype.hasOwnProperty.call()Object.hasOwn() 五种写法。本文保留原来的学习顺序,并补充空原型对象、Symbol 属性、继承属性和现代代码中的选择原则。

背景:先说清楚“存在”是什么意思

JavaScript 中至少有两种常见问题:

  1. 属性是否存在于对象及其原型链上:使用 inReflect.has()
  2. 属性是否是对象自己的属性:使用 Object.hasOwn()Object.prototype.hasOwnProperty.call()

不要把下面这种“值不是 undefined”的判断当作通用属性存在性检查:

const object = { count: 0, value: undefined }

object.count !== undefined // true:值为 0 不会导致假阴性
object.value !== undefined // false negative:属性存在,但值恰好是 undefined
object.missing !== undefined // false

undefined 可能本来就是属性值,所以“值不是 undefined”不能等价于“属性存在”。

下面的示例对象有自己的 name,同时从原型继承了 toString

const user = { name: '前端开发者' }

console.log('name' in user) // true
console.log('toString' in user) // true
console.log(Object.hasOwn(user, 'name')) // true
console.log(Object.hasOwn(user, 'toString')) // false

1. in 运算符:检查自身属性和继承属性

property in object 会沿着对象的原型链查找属性。左侧可以是字符串或 Symbol,右侧必须是对象或函数:

'name' in { name: '前端开发者' } // true
'age' in { name: '前端开发者' } // false

'toString' in {} // true,因为它来自 Object.prototype

in 判断的是属性键,不会读取属性值,也不会因为值是 undefined 就返回 false

const settings = { value: undefined }

'value' in settings // true
'missing' in settings // false

数组也可以使用 in,但它检查的是索引属性,而不是“值是否在数组中”:

const values = []
values[1] = undefined

0 in values // false:索引 0 是空位
1 in values // true:索引 1 是一个实际属性
'length' in values // true

如果只是想判断数组是否包含某个值,应使用 includes();如果想判断某个索引是否有属性,才考虑 index in array

in 还可以用于私有字段的品牌检查,但那是另一种语法:

class User {
  #id

  static hasBrand(value) {
    return #id in value
  }
}

普通属性判断仍然使用字符串或 Symbol 作为左操作数。

2. Reflect.has()in 的函数形式

Reflect.has(object, propertyKey)propertyKey in object 的属性查找语义相同:都会检查原型链:

const user = { name: '前端开发者' }

Reflect.has(user, 'name') // true
Reflect.has(user, 'toString') // true
Reflect.has(user, 'age') // false

它适合已经采用 Reflect 风格 API 的元编程代码,也方便作为回调传递。普通业务代码使用 inObject.hasOwn() 通常更直观。

const key = Symbol('cache')
const cache = { [key]: new Map() }

Reflect.has(cache, key) // true

Reflect.has()in 一样,要求第一个参数是对象;传入 nullundefined 会抛出 TypeError

3. hasOwnProperty():传统的自身属性检查

hasOwnProperty() 只检查对象自己的属性,不检查原型链:

const user = { name: '前端开发者' }

user.hasOwnProperty('name') // true
user.hasOwnProperty('toString') // false

但不建议无条件直接调用 object.hasOwnProperty(key),因为对象可能没有 Object.prototype,或者自己的属性覆盖了这个方法。

3.1 Object.create(null) 没有 hasOwnProperty

const dictionary = Object.create(null)
dictionary.name = '前端开发者'

// dictionary.hasOwnProperty('name')
// TypeError: dictionary.hasOwnProperty is not a function

3.2 自身属性可能覆盖同名方法

const object = {
  hasOwnProperty: '这是一个普通属性',
  name: '前端开发者'
}

// object.hasOwnProperty('name')
// TypeError: object.hasOwnProperty is not a function

因此,hasOwnProperty() 不是错误,但直接从不可信对象上取方法并调用并不安全。

4. Object.prototype.hasOwnProperty.call():兼容旧环境的安全写法

可以从稳定的原型方法上取出函数,再通过 call() 指定 this

function hasOwnLegacy(object, propertyKey) {
  return Object.prototype.hasOwnProperty.call(object, propertyKey)
}

hasOwnLegacy({ name: '前端开发者' }, 'name') // true
hasOwnLegacy({ hasOwnProperty: '覆盖了方法' }, 'name') // false
hasOwnLegacy(Object.create(null), 'name') // false
hasOwnLegacy({ toString: null }, 'toString') // true

这种写法可以处理空原型对象、覆盖同名属性和 Symbol 键:

const token = Symbol('token')
const object = { [token]: 123 }

Object.prototype.hasOwnProperty.call(object, token) // true

如果需要支持很老的 JavaScript 运行环境,这是可靠的兼容写法;现代代码通常优先使用下一节的 Object.hasOwn()

5. Object.hasOwn():现代的自身属性检查

Object.hasOwn(object, propertyKey) 在 ECMAScript 2022 中标准化,用来判断自身属性,语义上替代了常见的:

Object.prototype.hasOwnProperty.call(object, propertyKey)

示例:

const user = { name: '前端开发者' }

Object.hasOwn(user, 'name') // true
Object.hasOwn(user, 'toString') // false
Object.hasOwn(Object.create(null), 'name') // false
Object.hasOwn({ hasOwn: 'yes' }, 'hasOwn') // true

它也支持 Symbol 属性:

const id = Symbol('id')
const record = { [id]: 1 }

Object.hasOwn(record, id) // true

Object.hasOwn() 只判断自身属性,不判断属性是否可枚举、是否可写,也不会读取 getter 的返回值。需要检查这些信息时,应使用属性描述符:

const descriptor = Object.getOwnPropertyDescriptor(record, id)
console.log(descriptor?.enumerable)

对比表

方式检查原型链检查自身属性支持 Symbol推荐场景
key in object明确需要判断继承属性
Reflect.has(object, key)Reflect/元编程代码
object.hasOwnProperty(key)仅限确定对象没有覆盖方法的场景
Object.prototype.hasOwnProperty.call(...)旧环境或通用兼容函数
Object.hasOwn(object, key)现代业务代码首选

实际选择建议

function hasProperty(object, key) {
  // 需要包含继承属性
  return key in object
}

function hasOwnPropertySafe(object, key) {
  // 现代环境
  return Object.hasOwn(object, key)
}
  • 判断配置对象是否明确写入了某个字段:使用 Object.hasOwn()
  • 判断对象是否能响应某个继承来的方法:使用 inReflect.has(),但仍要考虑属性值是否可调用;
  • 处理 JSON、字典或外部输入时,不要假设对象一定继承自 Object.prototype
  • 需要兼容 Object.hasOwn() 不存在的旧运行环境时,使用 Object.prototype.hasOwnProperty.call()
  • 如果只想遍历可枚举自身字符串键,使用 Object.keys();如果还要包括 Symbol 键,使用 Reflect.ownKeys(),它们解决的是遍历问题,不是简单的属性存在性判断。

结论

原文中的五种方法仍然有学习价值,但现代项目不必平均使用它们:

  1. 需要查原型链:inReflect.has()
  2. 需要查自身属性:优先 Object.hasOwn()
  3. 兼容旧环境:Object.prototype.hasOwnProperty.call()
  4. 不要用 value !== undefined 代替属性存在性判断;
  5. 不要直接调用不可信对象上的 hasOwnProperty()

参考资料

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS