JavaScript中对象的属性分为两种:数据属性和访问器属性。然后根据具体的上下文环境的不同,又可以将属性分为:原型属性和实例属性。原型属性是定义在对象的原型(prototype
)中的属性,而实例属性一方面来自构造的函数中,然后就是构造函数实例化后添加的新属性。
在JavaScript中除了检测对象的属性是否存在,还会经常对对象的属性进行遍历(枚举)。而在JavaScript中遍历一个对象的属性并不太简单,主要有两个原因:
- JavaScript中的对象通常都处在某个原型链中,它会从一个或多个的上层原型上继承一些属性
- JavaScript中的属性不光有值,它还有一些除了值以外的其他特性,其中一个影响属性遍历的特性就是
[[Enumerable]]
,如果该值为true
,则这个属性是可枚举的,否则反之
这篇文章将总结有关于JavaScript中对象属性枚举的几种方法:
for ... in
Object.keys()
Object.getOwnPropertyNames()
for ... of
for ... in
for...in
循环可以遍历对象中所有可枚举的对象属性(包括对象自有属性和继承的属性)。不过需要注意的是,使用for...in
循环遍历对象属性时返回的属性会因为各个浏览器不同导致对象属性遍历的顺序有可能不是当初构建时的顺序。
var obj = {
'x': 1,
'y': 2,
'z': 3
}
obj.propertyIsEnumerable('toString'); // false,不可枚举
for (prop in obj) {
console.log(prop); // 输出x,y,z;但不会输出toString
}
其实for...in
操作的主要目的就是遍历对象的属性,如果只需要获取对象的实例属性(跳过继承属性),可以使用hasOwnProperty()
进行过滤:
for (prop in obj) {
if (!obj.hasOwnProperty(prop)) continue; // 跳过继承属性
}
如此一来,可以这样来使用for...in
循环遍历对象属性:
(function () {
var getEnumPropertyNames = function (obj) {
if (typeof obj !== 'object') throw TypeError(); // 参数必须是对象
var props = []; // 将要返回的数组
for (var prop in obj) { // 遍历所有可枚举的属性
if (obj.hasOwnProperty(prop)) { //判断是否是自有属性
props.push(prop); //将属性名添加到数组中
}
}
return props; //返回这个数组
}
// 实例化
var obj = {
'x': 1,
'y':2
}
obj.propertyIsEnumerable('toString')
var propertys = getEnumPropertyNames(obj);
console.log(propertys.length); //2
console.log(propertys.join(",")); //x,y
})()
Object.keys()
Object.keys()
方法会返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,数组中属性名的排列顺序和使用for...in
循环遍历该对象时返回的顺序一致。两者最大的区别在于for...in
还会遍历出一个对象从其原型链上继承到的可枚举属性。
Object.keys()
返回一个所有元素为字符串的数组,其元素来自于从给定的对象上面可直接枚举的属性。这些属性的顺序与手动遍历该对象属性时的一致。
var obj = {
'x': 1,
'y': 2,
'z': 3
}
obj.propertyIsEnumerable('toString');
console.log(Object.keys(obj)); // ["x", "y", "z"]
Object.keys()
可以遍历对象可枚举的自身属性。也就是说对象的属性不是从原型链上继承下来的。
注意:在 ES5 环境,如果传入的参数不是一个对象,而是一个字符串,那么它会报 TypeError。在 ES6 环境,如果传入的是一个非对象参数,内部会对参数作一次强制对象转换,如果转换不成功会抛出 TypeError。
// 在 ES5 环境
Object.keys('foo'); // TypeError: "foo" is not an object
// 在 ES6 环境
Object.keys('foo'); // ["0", "1", "2"]
// 传入 null 对象
Object.keys(null); // Uncaught TypeError: Cannot convert undefined or null to object
// 传入 undefined
Object.keys(undefined); // Uncaught TypeError: Cannot convert undefined or null to object
Object.getOwnPropertyNames()
Object.getOwnPropertyNames()
方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性)组成的数组,但不会获取原型链上的属性。该数组对元素是 obj
自身拥有的枚举或不可枚举属性名称字符串。 数组中枚举属性的顺序与通过for...in
(或 Object.keys()
)迭代该对象属性时一致。 数组中不可枚举属性的顺序未定义。
// 类数组对象var obj = {
0: "a",
1: "b",
2: "c"
};
console.log(Object.getOwnPropertyNames(obj).sort()); // ["0", "1", "2"]
for...of
for...of
为ES6新增的方法,主要来遍历可迭代的对象(包括Array
, Map
, Set
, arguments
等),它主要用来获取对象的属性值,而for...in
主要获取对象的属性名。
var colors = ['red', 'green', 'blue'];
colors.length = 5;
colors.push('yellow');
for (var i in colors) {
console.log(colors[i]); // red green blue yellow
}
for (var j of colors) {
console.log(j); // red green blue undefined undefined yellow
}
可以看到使用for...of
可以输出包括数组中不存在的值在内的所有值。
其实除了使用for...of
直接获取属性值外,我们也可以利用Array.prototype.forEach()
来达到同样的目的。
var colors = ['red', 'green', 'blue'];
colors.foo = 'hello';
Object.keys(colors).forEach(function(elem, index) {
console.log(colors[elem]); // red green blue hello
console.log(colors[index]); // red green blue undefined
});
colors.forEach(function(elem, index) {
console.log(elem); // red green blue
console.log(index); // 0 1 2
})
总结一下
其实这几个方法之间的差异主要在属性是否可可枚举,是来自己原型,还是实例。
方法 | 适用范围 | 描述 |
---|---|---|
for...in | 数组、对象 | 获取可枚举的实例和原型属性名 |
Object.keys() | 数组,对象 | 返回可枚举的实例属性名组成的数组 |
Object.getPropertyNames() | 数组、对象 | 返回除原型属性以外的所有属性(包括不可枚举的属性)名组成的数组 |
for...of | 可迭代对象(Array , Map , Set , arguments 等) | 返回属性值 |