一、前言
我们知道,在js的对象中,有下面几种属性:
- 可枚举属性
- 不可枚举属性
- 从原型链上继承的属性
- 以Symbol作为key值
今天,我们就来谈一谈如何遍历这些属性。
二、遍历可枚举属性
首先,遍历可枚举属性是非常常见的一个需求,我们平时比较常用的方法是for in和Object.keys(),这两个方法都能遍历可枚举属性,我们看看如下的代码:
let obj = {
name: "klx",
sex: "man",
age: 21
}
Object.keys(obj).forEach((key, index) => {
console.log(`${key}:${obj[key]}`);
})
//name:klx
//sex:man
//age:21
for(key in obj) {
console.log(`${key}:${obj[key]}`);
}
//name:klx
//sex:man
//age:21
可以看到,他们并没有什么区别。那么,这两个方法是完全一样的吗?我们再来看看下面的代码:
let obj = {
name: "klx",
sex: "man",
age: 21
}
let parent = {
parentName: "parent",
parentSex: "man",
parentAge:40
}
Object.setPrototypeOf(obj, parent);
Object.keys(obj).forEach((key, index) => {
console.log(`${key}:${obj[key]}`);
})
//name:klx
//sex:man
//age:21
for(key in obj) {
console.log(`${key}:${obj[key]}`);
}
//name:klx
//sex:man
//age:21
//parentName:parent
//parentSex:man
//parentAge:40
这时我们可以看到,第二种方法比第一种多出来几个属性。所以,他们的区别就体现出来了,我们在上面的代码中将obj的原型对象设置为了另一个对象,而for in把原型链上的属性也遍历了出来,但是Object.keys没有。所以,他们两个的区别就是:for in会遍历原型链上的属性,Object.keys不会遍历原型链上的属性。所以,我们平时最好使用Object.keys去遍历对象,会避免一些不必要的麻烦。
二、遍历不可枚举属性
什么是不可枚举属性?如果不知道这个问题的同学可以参考MDN的介绍,这里我就不详细解释了。我们先来看看下面的代码:
let obj = {
name: "klx",
sex: "man",
age: 21
}
Object.defineProperty(obj, 'age', {
enumerable:false,
configurable:true,
writable:true
})
Object.keys(obj).forEach((key, index) => {
console.log(`${key}:${obj[key]}`);
})
//name:klx
//sex:man
for(key in obj) {
console.log(`${key}:${obj[key]}`);
}
//name:klx
//sex:man
在这里,我们把age改成了不可枚举属性(因为我们的年龄不能随便告诉别人啊~)。所以可以看见,不管我们用for in还是Object.keys都无法遍历到age属性。那么,我们这时该如何才能遍历到age这个属性呢?
我们可以使用Object.getOwnPropertyNames()这个方法,来看下面的代码
let obj = {
name: "klx",
sex: "man",
age: 21,
}
let a = Symbol('a');
obj[a] = 'Symbol';
Object.defineProperty(obj, 'age', {
enumerable:false,
configurable:true,
writable:true
})
let parent = {
parentName: "parent",
parentSex: "man",
parentAge:40
}
Object.setPrototypeOf(obj, parent);
Object.keys(obj).forEach((key, index) => {
console.log(`${key}:${obj[key]}`);
})
//name:klx
//sex:man
for(key in obj) {
console.log(`${key}:${obj[key]}`);
}
//name:klx
//sex:man
//parentName:parent
//parentSex:man
//parentAge:40
Object.getOwnPropertyNames(obj).forEach(key => {
console.log(`${key}:${obj[key]}`);
})
//name:klx
//sex:man
//age:21
所以,使用Object.getOwnPropertyNames就能遍历到这个对象除掉以Symbol作为名称的属性,同时也不能遍历到原型链上的属性。
三、遍历以Symbol作为key值的属性
那么,如果我们想要遍历以Symbol作为key值的属性该怎么办呢?这时我们可以用这个方法:Object.getOwnPropertySymbols。
let obj = {
name: "klx",
sex: "man",
age: 21,
}
let a = Symbol('a');
obj[a] = 'Symbol';
Object.defineProperty(obj, 'age', {
enumerable:false,
configurable:true,
writable:true
})
let parent = {
parentName: "parent",
parentSex: "man",
parentAge:40
}
Object.setPrototypeOf(obj, parent);
Object.keys(obj).forEach((key, index) => {
console.log(`${key}:${obj[key]}`);
})
//name:klx
//sex:man
for(key in obj) {
console.log(`${key}:${obj[key]}`);
}
//name:klx
//sex:man
//parentName:parent
//parentSex:man
//parentAge:40
Object.getOwnPropertyNames(obj).forEach(key => {
console.log(`${key}:${obj[key]}`);
})
//name:klx
//sex:man
//age:21
Object.getOwnPropertySymbols(obj).forEach(key => {
console.log(obj[key]);
})
//Symbol
那么问题来了,如果我想遍历一个对象上的全部属性,又该怎么做呢?这时我们可以用Reflect.ownKeys()方法来达到我们的目的,他会放回该对象上的所有属性,但是不包括原型链上的属性:
let obj = {
name: "klx",
sex: "man",
age: 21,
}
let a = Symbol('a');
obj[a] = 'Symbol';
Object.defineProperty(obj, 'age', {
enumerable:false,
configurable:true,
writable:true
})
let parent = {
parentName: "parent",
parentSex: "man",
parentAge:40
}
Object.setPrototypeOf(obj, parent);
console.log(Reflect.ownKeys(obj));
//[ 'name', 'sex', 'age', Symbol(a) ]
四、遍历自身以及原型链上的所有属性
那么,如果我不仅仅想遍历自身所有属性,还想遍历原型链上的所有属性,该怎么办?这时,就没有现成的api供我们调用了,但是我们可以自己写一个方法来遍历:
let obj = {
name: "klx",
sex: "man",
age: 21,
}
let a = Symbol('a');
obj[a] = 'Symbol';
Object.defineProperty(obj, 'age', {
enumerable:false,
configurable:true,
writable:true
})
let parent = {
parentName: "parent",
parentSex: "man",
parentAge:40
}
Object.setPrototypeOf(obj, parent);
function traverse(obj, arr) {
arr.push(...Reflect.ownKeys(obj));
if(Object.getPrototypeOf(obj)) {
traverse(Object.getPrototypeOf(obj), arr);
}
return arr;
}
console.log(traverse(obj, []));
/*
[ 'name',
'sex',
'age',
Symbol(a),
'parentName',
'parentSex',
'parentAge',
'constructor',
'__defineGetter__',
'__defineSetter__',
'hasOwnProperty',
'__lookupGetter__',
'__lookupSetter__',
'isPrototypeOf',
'propertyIsEnumerable',
'toString',
'valueOf',
'__proto__',
'toLocaleString' ]
*/
这样,我们就把该对象以及原型链上的属性全部遍历出来了,当然,你也可以根据自己的需求,利用上述几个方法组合,来遍历自己想要的属性。
五、结语
不要小看一个简单的问题,其中可能有许多你不知道的小知识。我们平时简单遍历一个对象可能单纯使用for in就能完成我们的工作了,但是这些更多的细节问题你有注意到吗?好了,这篇博客就到这里了,希望能带给大家一些东西~