作者:
Barry Zhang(张新强)
原文:www.barryzhang.com/archives/521
(
点击文末阅读原文即可前往
)
编程这么多年,要是每次写遍历代码时都用 for 循环,真心感觉对不起 JavaScript 语言~
对象遍历
为了便于对象遍历的测试,我在下面定义了一个测试对象
obj
。
测试对象
// 为 Object 设置三个自定义属性(可枚举)
Object
.prototype.userProp =
'userProp'
;
Object
.prototype.getUserProp =
function
()
{
return
Object
.prototype.userProp;
};
// 定义一个对象,隐式地继承自 Object.prototype
var
obj = {
name:
'percy'
,
age:
21
,
[
Symbol
(
'symbol 属性'
)]:
'symbolProp'
,
unEnumerable:
'我是一个不可枚举属性'
,
skills: [
'html'
,
'css'
,
'js'
],
getSkills:
function
()
{
return
this
.skills;
}
};
// 设置 unEnumerable 属性为不可枚举属性
Object
.defineProperty(obj,
'unEnumerable'
, {
enumerable:
false
});
ES6 之后,共有以下 5 种方法可以遍历对象的属性。
for…in: 遍历对象自身的和继承的可枚举属性(不含 Symbol 类型的属性
for
(
let
key
in
obj ) {
console
.log( key );
console
.log( obj.key );
console
.log( obj[key] );
}
不要使用 for…in 来遍历数组
,虽然可以遍历,但是如果为 Object.prototype 设置了可枚举属性后,也会把这些属性遍历到,因为数组也是一种对象。
Object.keys(obj):返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 类型的属性)
Object
.keys(obj);
// ["name", "age", "skills", "getSkills"]
Object.getOwnPropertyNames(obj):返回一个数组,包含对象自身的所有属性(不含 Symbol 类型的属性,不包含继承属性,但是包括不可枚举属性)
Object
.getOwnPropertyNames(obj);
// ["name", "age", "unEnumerable", "skills", "getSkills"]
Object.getOwnPropertySymbols(obj):返回一个数组,包含对象自身的所有 Symbol 类型的属性(不包括继承的属性)
Object
.getOwnPropertyNames(obj);
// [Symbol(symbol 属性)]
Reflect.ownKeys(obj):返回一个数组,包含对象自身的所有属性(包含 Symbol 类型的属性,还有不可枚举的属性,但是不包括继承的属性)
Reflect
.ownKeys(obj);
// ["name", "age", "unEnumerable", "skills", "getSkills", Symbol(symbol 属性)]
以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则
1)首先遍历所有属性名为数值的属性,按照数字排序
2)其次遍历所有属性名为字符串的属性,按照生成时间排序
3)最后遍历所有属性名为Symbol值的属性,按照生成时间排序
如何判断某个属性是不是某个对象自身的属性呢?
用 in 操作符(不严谨,它其实判定的是这个属性在不在该对象的原型链上)
'age'
in
obj;
'userProp'
in
obj;
'name'
in
Object
;
// 上面这个也是 true 的原因是,Object 是一个构造函数,而函数恰巧也有一个 name 属性
Object.name; // 'Object'
Array
.name;
用 hasOwnProperty(),这个方法只会检测某个对象上的属性,而不是原型链上的属性。
obj.hasOwnProperty(
'age'
);
obj.hasOwnProperty(
'skills'
);
obj.hasOwnProperty(
'userProp'
);
但是它还是有不足之处的。举例~
// 利用 Object.create() 新建一个对象,并且这个对象没有任何原型链
var
obj2 =
Object
.create(
null
, {
name: { value:
'percy'
},
age: { value:
21
},
skills: { value: [
'html'
,
'css'
,
'js'
] }
});
obj2.hasOwnProperty('name'); // 报错
obj2.hasOwnProperty(
'skills'
);
针对上面的情况,我们用一个更完善的解决方案来解决。
使用 Object.prototype.hasOwnProperty.call(obj,’prop’…)
Object
.prototype.hasOwnProperty.call(obj2,
'name'
);
Object
.prototype.hasOwnProperty.call(obj2,
'skills'
);
Object
.prototype.hasOwnProperty.call(obj2,
'userProp'
);
数组遍历
数组实际上也是一种对象,所以也可以使用上面对象遍历的任意一个方法(但要注意尺度),另外,数组还拥有其他遍历的方法。
最基本的 for 循环、while 循环遍历(缺陷是多添加了一个计数变量)
ES6 引入:for…of ,这下就没有这个计数变量了,但是也不够简洁(这里不做详细介绍,以后写)
for(let value of arr){
console
.log(value);
}
下面说几种数组内置的一些遍历方法
Array
.prototype.forEach(callback(currentValue, index, array){
// do something
}[,thisArg]);
// 如果数组在迭代时被修改了,则按照索引继续遍历修改后的数组
var
words = [
"one"
,
"two"
,
"three"
,
"four"
];
words.forEach(
function
(
word
)
{
console
.log(word);
if
(word ===
"two"
) {
words.shift();
}
});
// one
// two
// four
Array.prototype.map(): 返回一个新数组,每个元素都是回调函数返回的值
Array
.prototype.map(callback(currentValue, index, array){
// do something
}[,thisArg]);
```
`
``
js
// map 的一个坑
[
1
,
2
,
3
].map(
parseInt
);
// 提示 map(currentValue,index,array)
// parseInt(value,base)
一些有用的数组内置方法(类似 map,回调函数的参数都是那 3 个)
Array.prototype.reduceRight(callback[, initialValue]): 用法和上面的函数一样,只不过遍历方向正好相反
// 一些相关的案例
// 对数组进行累加、累乘等运算
[
1
,
10
,
5
,
3
,
8
].reduce(
function
(
accumulator,currentValue
)
{
return
accumulator*currentValue;
});
// 数组扁平化
[[
0