JavaScript迭代操作总结

普通for循环

优点:没有任何额外的函数调用栈和上下文,在大数据量的情况下,性能更好

缺点:需要额外维护变量i和迭代边界

forEach方法

forEach() 方法按升序为数组中含有效值的每一项执行一次 callback 函数,那些已删除或者未初始化的项将被跳过(例如在稀疏数组上)。

可依次向 callback 函数传入三个参数:

  1. 数组当前项的值
  2. 数组当前项的索引
  3. 数组对象本身

缺点:

  1. 只能用来对数组进行迭代操作
  2. 除了抛出异常以外,没有办法中止或跳出 forEach()循环

for…in

for...in语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。有三个要注意的点:

  1. 只能访问到除symbol以外的可枚举属性。

    JavaScript中基本包装类型的原型属性是不可枚举的,如Object, Array, Number等,或者属性的enumerable属性被设置为false,这些属性都不能被for…in访问到。可以使用Object.getOwnPropertyDescriptor(targetObj, attrName)方法来查看对象的某个属性是否可枚举

  2. 非基本包装类型的原型上的属性和手动添加的其他属性,如果是可枚举的,都可以被遍历到。

    如果你只要考虑对象本身的属性,而不是它的原型,那么使用 getOwnPropertyNames()或执行 hasOwnProperty()来确定某属性是否是对象本身的属性

  3. 迭代的顺序并不一定会按照键名的书写顺序,所以for…in不应该用于迭代一个关注索引顺序的数据结构,更适合遍历对象。背后的执行机制可以看这一篇:for…in和Object.keys迭代顺序的深入分析

  4. 支持break/continue的操作

for…of

for...of语句在可迭代对象(包括 ArrayMapSetStringTypedArrayarguments对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句。

需要注意的是,for...of是依靠对象的迭代器工作的,也就是该对象必须部署Symbol.iterator属性,而普通的对象并没有iterator接口,所以不能使用for...of迭代普通对象。可以通过给普通对象实现Symbol.iterator接口的方式来让普通对象变得可迭代。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let father = {
*[Symbol.iterator]() {
for (let key of Reflect.ownKeys(this)) {
yield key;
}
}
};

let obj = Object.create(father);

obj.a = 1;
obj[0] = 1;
obj[Symbol('PaperCrane')] = 1;
Object.defineProperty(obj, 'b', {
writable: true,
value: 1,
enumerable: false,
configurable: true
});

for (let key of obj) {
console.log(key);
}

// 0
// a
// b
// Symbol(PaperCrane)