JavaScript 中获取对象属性的若干方法比较

对象的属性有这么几种情况:

  1. 是否可枚举
  2. 是否是 Symbol
  3. 是否属于自身

所以一个对象的属性的性质有 2^3 = 8 种情况。下面我们就来做个小实验:

let sa = Symbol()
let _sa = Symbol()
let a = {a: true, [sa]: true} // a 是普通属性,sa 是 Symbol 属性,默认都是可枚举的
Object.defineProperty(a, '_a', { // _a 普通不可枚举属性
	enumerable: false,
	value: true
})
Object.defineProperty(a, _sa, { // _sa Symbol 不可枚举属性
	enumerable: false,
	value: true
})

let sb = Symbol()
let _sb = Symbol()
let b = Object.create(a) // a 在 b 的原型链上
b.b = true // b 是普通可枚举属性
b[sb] = true // sb 是 Symbol 可枚举属性
Object.defineProperty(b, '_b', { // _b 是普通不可枚举属性
	enumerable: false,
	value: true
})
Object.defineProperty(b, _sb, { // _sb 是 Symbol 不可枚举属性
	enumerable: false,
	value: true
})

对于 b 来说,一共有这么几种属性:

属性是否自身是否 Symbol是否可枚举
b
_b
sb
_sb
a
_a
sa
_sa

是否 b 自有属性就看属性名里是否有 b,是否 Symbol 就看属性名里是否有 s,是否可枚举就看属性名里的 _

for (let prop in b) console.log(prop) // b, a

Object.keys(b) // b

Object.getOwnPropertyNames(b) // b, _b

Object.getOwnPropertySymbols(b) // sb, _sb

Reflect.ownKeys(b) // b, _b, sb, _sb

由此我们可以看到:

  • for...in => 获取当前对象及其原型链上所有可枚举且非Symbol属性
  • Object.keys => 获取当前对象上所有可枚举且非Symbol属性
  • Object.getOwnPropertyNames => 获取当前对象上所有非Symbol属性
  • Object.getOwnPropertySymbols(b) => 获取当前对象上所有Symbol属性
  • Reflect.ownKeys => 获取当前对象所有属性

这么来看其实还是有点绕,我这里总结一下就是:

  1. 在 Symbol 出现以前,只有是否可枚举,以及是否原型链的区别。 而跟原型链相关的只有 for...in。其他的都是和自身属性相关。
  2. 而获取自身属性分为是否可枚举,因此有 Object.keys 获取可枚举的,Object.getOwnPropertyNames 获取所有的。
  3. 后面出了 Symbol 的概念,所以有了针对获取所有 Symbol 的方法就是:Object.getOwnPropertySymbols
  4. 这个时候如果想获取所有自身属性的时候,就比较麻烦,所以有了 Reflect.ownKeys

这里其实有个小问题,那就是我们无法区分 Symbol 是否可枚举。

我们可以通过 {Object.keys} / {Object.getOwnPropertyNames} - {Object.keys} 区分 可枚举普通属性/不可枚举普通属性

而对于Symbol属性,我们只能获取所有的,无法区分是否可枚举。

所以,js还提供了一个方法 obj.propertyIsEnumerable 来判断自有属性是否可枚举。

另外还有一个更强大的方法:Object.getOwnPropertyDescriptor 能够获取自有属性的相关描述,包括是否可枚举,是否可写,是否可配置等。

情景

  1. 获取对象自身可枚举非 Symbol 属性:
    Object.keys(b) // b
    
  2. 获取对象自身可枚举 Symbol 属性:
    // 方法 1
    Object.getOwnProerptySymbols(b).filter(prop => b.propertyIsEnumerable(prop)) // sb
    // 方法 2
    Object.getOwnProerptySymbols(b).filter(prop => Object.getOwnPropertyDescriptor(b, prop).enumerable) // sb
    
  3. 获取对象自身所有可枚举属性:
    Reflect.ownKeys(b).filter(prop => b.propertyIsEnumerable(prop)) // b, sb
    
  4. 获取对象自身所有属性
    Reflect.ownKeys(b) // b, sb, _b, _sb
    

总结

  • 考虑原型链时只要考虑 for...in, 否则
  • 和 Symbol 不相关时,只要考虑 Object.keys, Object.getOwnPropertyNames
  • 和 Symbol 相关时,只要考虑 Reflect.ownKeys, Object.getOwnPropertySymbols
  • 是否可枚举使用 obj.propertyIsEnumerableObject.getOwnPropertyDescriptor 来判断。