写在前面
在ECMAScript
中,通过key
获取Object
的value
是再平常不过的操作。可是,由于原型链(prototype chain)、类继承(class extend)的存在,在你遍历对象属性的时候,你想要/不想要获取原型链上,或者父类的属性,结果都会不如你的预期。还有一种情况,你要取到Symbol
的属性,却无法取得。
本片文章将会对比几种遍历对象属性的方法,让你可以准确的获取到对象上的值。
方法对比
我们先使用构造函数创建一个对象,在构造函数中设置其实例属性,然后设置构造函数原型对象的属性,然后再给实例添加属性。
// 构造函数
function MakeObj () {
Object.defineProperties(this, {
x1: {
value: 1,
enumerable: true
},
x2: {
value: 11,
enumerable: false
}
})
}
// 原型对象
Object.defineProperties(MakeObj.prototype, {
y1: {
value: 2,
enumerable: true
},
y2: {
value: 22,
enumerable: false
}
})
// 创建实例
var obj = new MakeObj()
// 设置实例对象属性
Object.defineProperties(obj, {
z1: {
value: 3,
enumerable: true
},
z2: {
value: 33,
enumerable: false
}
})
复制代码
我们对比一下Object.keys
、 Object.getOwnPropertyNames
、 for...in
、 for...of
。
Object.keys(obj) // ["x1", "z1"]
Object.getOwnPropertyNames(obj) // ["x1", "x2", "z1", "z2"]
var forIn = []
for (let x in obj) {
forIn.push(x)
}
forIn // ["x1", "z1", "y1"]
var forOf = []
for (let x in obj) {
forOf.push(x)
}
forOf // ["x1", "z1", "y1"]
复制代码
用表格看更清晰:
方法 | 实例属性 | 原型对象属性 | ||
---|---|---|---|---|
可枚举(x1,z1) | 不可枚举(x2,z2) | 可枚举(y1) | 不可枚举(y2) | |
Object.keys | √ | × | × | × |
Object.getOwnPropertyNames | √ | √ | × | × |
for...in | √ | × | √ | × |
for...of | √ | × | √ | × |
所以,如果你想要:
- 仅遍历可枚举的实例属性,使用
Object.keys
- 遍历包括枚举和不可枚举的实例属性,使用
Object.getOwnPropertyNames
- 如果你要遍历包括可枚举的实例属性和原型对象上的可枚举属性,使用
for...in
和for...of
获取Symbol
属性值
如果我们设置了Symbol属性呢?
var sml = Symbol('key')
var obj = {
x: 1,
[sml]: 'symbol value'
}
var forIn = []
for (let x in obj) {
forIn.push(x)
}
var forOf = []
for (let x in obj) {
forOf.push(x)
}
Object.keys(obj).includes(sml) // false
Object.getOwnPropertyNames(obj).includes(sml) // false
forIn.includes(sml) // false
forOf.includes(sml) // false
Object.getOwnPropertySymbols(obj).includes(sml) // true
复制代码
我们看到上面提到的几个方法都是无法获取到 Symbol
值的。所以, ECMAScript
提供了Object.getOwnPropertySymbols
方法来遍历实例属性是Symbol
的对象。
我们继续看一下这个方法对不可枚举属性以及原型对象属性的遍历:
var ctor = function() {}
var makeSml = key => Symbol(key)
// 原型对象
Object.defineProperties(ctor.prototype, {
[makeSml('y1')]: {
value: 2,
enumerable: true
},
[makeSml('y2')]: {
value: 22,
enumerable: false
}
})
var obj = new ctor()
// 设置实例对象属性
Object.defineProperties(obj, {
[makeSml('z1')]: {
value: 3,
enumerable: true
},
[makeSml('z2')]: {
value: 33,
enumerable: false
}
})
Object.getOwnPropertySymbols(obj) // [Symbol(z1), Symbol(z2)]
复制代码
我们还是用表格展示一下:
方法 | 实例属性 | 原型对象属性 | ||
---|---|---|---|---|
可枚举(z1) | 不可枚举(z2) | 可枚举(y1) | 不可枚举(y2) | |
Object.getOwnPropertySymbols | √ | √ | × | × |
所以, Object.getOwnPropertySymbols
仅可以获取到实例属性为 Symbol
的属性值。
总结
由于 ECMAScript
版本不断更新发布,这门语言会在兼容一下老的设计和新的设计中变得越来越复杂、臃肿。简单的一个获取对象属性值,却提供了好几个方法。并且,有时候你还要组合使用它们才能达到你的需求,比如你要获取一个对象的所有实例属性值,无论普通还是 symbol
,那么你可能就要组合使用 Object.getOwnPropertyNames
和 Object.getOwnPropertySymbols
,如果你只取可枚举的普通实例属性值,那么你还要取Object.getOwnPropertyNames
和 Object.keys
的交集。
在一般的业务场景中,我们可能用不到这些方法,很多时候,一个Object.keys
就能满足需求。但在构建一些基础库(例如 lodash
),或者框架的时候(例如 vue
)时,我们必须考虑到这些。