在数字索引的数组中,典型的迭代所有的值的办法是使用标准的 for 循环,比如:

但是这并没有迭代所有的值,而是迭代了所有的下标,然后由你使用索引来引用值,比如 myArray[i]

ES5 还为数组加入了几个迭代帮助方法,包括 forEach(..)every(..)、和 some(..)。这些帮助方法的每一个都接收一个回调函数,这个函数将施用于数组中的每一个元素,仅在如何响应回调的返回值上有所不同。

forEach(..) 将会迭代数组中所有的值,并且忽略回调的返回值。every(..) 会一直迭代到最后,或者 当回调返回一个 false(或“falsy”)值,而 some(..) 会一直迭代到最后,或者 当回调返回一个 true(或“truthy”)值。

这些在 every(..)some(..) 内部的特殊返回值有些像普通 for 循环中的 break 语句,它们可以在迭代执行到末尾之前将它结束掉。

如果你使用 for..in 循环在一个对象上进行迭代,你也只能间接地得到值,因为它实际上仅仅迭代对象的所有可枚举属性,让你自己手动地去访问属性来得到值。

但是如果你想直接迭代值,而不是数组下标(或对象属性)呢?ES6 加入了一个有用的 for..of 循环语法,用来迭代数组(和对象,如果这个对象有定义的迭代器):

for..of 循环要求被迭代的 东西 提供一个迭代器对象(从一个在语言规范中叫做 的默认内部函数那里得到),每次循环都调用一次这个迭代器对象的 next() 方法,循环迭代的内容就是这些连续的返回值。

数组拥有内建的 @,所以正如展示的那样,for..of 对于它们很容易使用。但是让我们使用内建的 @@iterator 来手动迭代一个数组,来看看它是怎么工作的:

注意: 我们使用一个 ES6 的 SymbolSymbol.iterator 来取得一个对象的 @ 内部属性。我们在本章中简单地提到过 Symbol 的语义(见“计算型属性名”),同样的原理也适用于这里。你总是希望通过 Symbol 名称,而不是它可能持有的特殊的值,来引用这样特殊的属性。另外,尽管这个名称有这样的暗示,但 @@iterator 本身 不是迭代器对象, 而是一个返回迭代器对象的 方法 —— 一个重要的细节!

正如上面的代码段揭示的,迭代器的 next() 调用的返回值是一个 { value: .. , done: .. } 形式的对象,其中 value 是当前迭代的值,而 done 是一个 boolean,表示是否还有更多内容可以迭代。

注意值 3done:false 一起返回,猛地一看会有些奇怪。你不得不第四次调用 next()(在前一个代码段的 for..of 循环会自动这样做)来得到 ,以使自己知道迭代已经完成。这个怪异之处的原因超出了我们要在这里讨论的范围,但是它源自于 ES6 生成器(generator)函数的语义。

但是 可以 为你想要迭代的对象定义你自己的默认 @。比如:

注意: 我们使用了 Object.defineProperty(..) 来自定义我们的 @@iterator(很大程度上是因为我们可以将它指定为不可枚举的),但是通过将 Symbol 作为一个 计算型属性名(在本章前面的部分讨论过),我们也可以直接声明它,比如 var myObject = { a:2, b:3, [Symbol.iterator]: function(){ /* .. */ } }

每次 for..of 循环在 myObject 的迭代器对象上调用 next() 时,迭代器内部的指针将会向前移动并返回对象属性列表的下一个值(关于对象属性/值迭代顺序,参照前面的注意事项)。

我们刚刚演示的迭代,是一个简单的一个值一个值的迭代,当然你可以为你的自定义数据结构定义任意复杂的迭代方法,只要你觉得合适。对于操作用户自定义对象来说,自定义迭代器与 ES6 的 for..of 循环相组合,是一个新的强大的语法工具。

举个例子,一个 Pixel(像素) 对象列表(拥有 xy 的坐标值)可以根据距离原点 (0,0) 的直线距离决定它的迭代顺序,或者过滤掉那些“太远”的点,等等。只要你的迭代器从 next() 调用返回期望的 { value: .. } 返回值,并在迭代结束后返回一个 { done: true } 值,ES6 的 for..of 循环就可以迭代它。

其实,你甚至可以生成一个永远不会“结束”,并且总会返回一个新值(比如随机数,递增值,唯一的识别符等等)的“无穷”迭代器,虽然你可能不会将这样的迭代器用于一个没有边界的 循环,因为它永远不会结束,而且会阻塞你的程序。