Iterators 与 for-of 循环

循环遍历数组的几种方式

for循环

1
2
3
4
var myArray = [1, 2, 3]
for (var index = 0; index < myArray.length; index++) {
console.log(myArray[index]);
}

ES5 forEach

1
2
3
4
var myArray = [1, 2, 3]
myArray.forEach(function (value) {
console.log(value);
});

forEach的内部函数无法使用 breakreturn(?)

for-in

1
2
3
4
var myArray = [1, 2, 3]
for (var index in myArray) {
console.log(myArray[index]);
}

for–in循环遍历数组不可取:

  • index是字符串而不是数字: "0", "1", "2"

  • 有时候不以正常的顺序循环遍历

  • 无法正常遍历某些数组, 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a = [] 
a[5] = 3

for (var i = 0; i < a.length; i++) {
console.log(a[i]);
}
/* 正常for循环结果:
undefined
undefined
undefined
undefined
undefined
3
*/
1
2
3
4
5
6
7
8
9
10
var a = []
a[5] = 3
for (var x in a) {
// 忽略了数组元素中的前5个元素, 只有3被输出
console.log(a[x])
}

/* for-in结果:
3
*/
  • 数组中若有可枚举属性(enumerable property)?,也会被遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
Array.prototype.foo = 1
var a = [1, 2, 3, 4, 5]
for (var x in a){
console.log(x)
}
/* 输出:
0
1
2
3
4
foo
*/

for-of

  • 遍历数组最简单直接的方式
  • 没有 for-in, forEach存在的那些缺陷
  • for-in的区别:
    for-in循环遍历对象属性
    for–of循环遍历数据(如数组中的各个元素)

  • for-of还可以循环遍历其它类型的集合, 如类数组对象: NodeLists, 字符串等:

1
2
3
for (var chr of "😺😲") {
console.log(chr)
}
  • 也可以用于遍历SetMap对象:
1
2
3
4
5
var words = ['a', 'b', 'c']
var uniqueWords = new Set(words)
for(var word in uniqueWords){
console.log(word)
}

对于Map对象, 需要先解构再遍历:

1
2
3
for (var [key, value] of phoneBookMap) {
console.log(key + "'s phone number is: " + value);
}
  • 不过for-of不可直接用于遍历一般的对象(plain object), 如果想要遍历这种对象的属性, 应该借助for-in或者Object.keys():
1
2
3
4
var someObject = { 0: 'a', 1: 'b', 2: 'c', 'hello': 'world' };
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}

for-of的原理

for-of通过调用对象方法起作用, Array, Map, Set等可遍历的对象都有个共同特点, 它们有遍历器(iterator)方法.

for-of无法遍历一般的对象是因为一般对象的属性中没有遍历器方法, 不过我们可以手动在对象原型中添加该方法myObject[Symbol.iterator](), 就能够使该对象可以被for-of循环遍历:

使用for–of循环遍历对象, 首先调用了[Symbol.iterator]()方法, 调用后的结果是返回一个新的可遍历的对象, 这个对象的属性中有个next()方法, for-of在每一次循环中都会调用一次该方法. 以下是一个简单的可遍历对象的例子:

1
2
3
4
5
6
7
8
var zeroesForeverIterator = {
[Symbol.iterator]: function () {
return this;
},
next: function () {
return {done: false, value: 0};
}
}

对于以上的对象, 每次next()被调用, 都会返回同样的结果, 告诉for-of循环: 1).遍历还未结束; 2). 下一个值是0.
此时,for (value of zeroesForeverIterator) {}就是个无限循环.

含有遍历器方法的对象还实现了可选的.return()方法和.throw(exc)方法. 如果循环由于抛出异常或breakreturn声明过早退出, for-of会调用.return(). 不过大部分情况下, 不需要用到.return()方法, .throw(exc)更特殊, for-of从不调用该方法. (more about it?)

1
2
3
for (VAR of ITERABLE) {
STATEMENTS
}

以上for-of循环, 实际上执行的操作是(简单版本):

1
2
3
4
5
6
7
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
VAR = $result.value;
STATEMENTS
$result = $iterator.next();
}

以上只是简单的版本, 大致体现了for-of是如何实现的, 实际上更复杂, 但对于理解上并没有太大的帮助, 因此省略某些方法的实现(例如是如何处理return()的). for-of很好用, 但是底层的实现比较复杂.

使用for-of

截至原文完成的时间(2015-04-29), 某些浏览器还不支持for-of, 在Chrome中, 可打开chrome://flags找到“Experimental JavaScript”并进行启用. 在服务端想要使用该特性, 在执行Node指令时加上--harmony选项.

Resources

Next articles will be:

Questions In This Article: cmd + f + ?