平时在业务写需求的时候总是会涉及到获取对象属性的需求。比如你可以像下面这么做:
const obj = {
prototype1: 1,
prototype2: null,
prototype3: 3
}
for (let key in obj) {
console.log(key) // 'prototype1' 'prototype2' 'prototype3'
}
- hasOwnProperty
但是如果你以为这样就万事大吉就错了,因为for in还可以查找遍历原型链上的属性,从而达到和你错误的预期。比如以下:
Object.prototype.prototype4 = 4
const obj = {
prototype1: 1,
prototype2: null,
prototype3: 3
}
for (let key in obj) {
console.log(key) // 'prototype1' 'prototype2' 'prototype3' 'prototype4'
}
所以我们用for in遍历对象的属性的时候需要过滤掉来自于原型链上的属性,hasOwnProperty方法接受一个字符串或者symbol用来返回对象上是否存在该属性,且该方法只会检测自身属性。所以代码可以作如下改造:
Object.prototype.prototype4 = 4
const obj = {
prototype1: 1,
prototype2: null,
prototype3: 3
}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key) // 'prototype1' 'prototype2' 'prototype3'
}
}
但是如果你以为这样就安全就太年轻了,因为hasOwnProperty作为对象的一个属性来讲是可以被重写的,所以我们最好不要直接用对象自身的hasOwnProperty,而是如下:
Object.prototype.prototype4 = 4
const obj = {
prototype1: 1,
prototype2: null,
prototype3: 3
}
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
console.log(key)
}
}
- enumerable
其实每个对象上面都会有很多属性,比如上面的hasOwnProperty也是对象的一个属性,那么为什么这些属性for in遍历不到呢?因为有些属性是不可枚举的,也就是说该属性的描述符enumerable为false,我们可以打印一下obj的一个属性来看一下该属性的描述符:
console.log(Object.getOwnPropertyDescriptor(obj, 'prototype1'))
我们可以看到打印了一个对象,其中enumerable为true。其实我们也可以设置对象的属性为不可枚举,然后在用for in看是否可以遍历的到:
Object.prototype.prototype4 = 4
const obj = {
prototype1: 1,
prototype2: null,
prototype3: 3
}
Object.defineProperty(obj, 'prototype1', {
enumerable: false
})
for (let key in obj) {
if (Object.hasOwnProperty.call(obj, key)) {
console.log(key) // 'prototype2' 'prototype3'
}
}
这里我们使用defineProperty去设置对象的属性的属性描述符,该方法第一个参数为对象,第二个参数为对象的属性,第三个参数为属性描述符。我们看到了prototype1没有被遍历到。
- configurable value writable
作为对象属性的属性描述符除了enumerable代表是否可枚举之外,还有configurable value writable这三个描述符,其中value很好理解,就是属性对应的值。configurable描述符为true的时候代表该属性可以被删除,writable为true代表可以修改该属性的value。 - 结论
我们获取对象属性的时候,最好使用Object.keys()方法。该方法会直接返回对象自身的所有可枚举属性的字符串数组。