JavaScript 提供了许多迭代集合的方法,从简单的 for 循环到 forEach、 map 和 filter。其中 while、do...while、for、for...in、for...of,可以被 label 标识,可以被 break 或者 continue 来指出程序是否该停止循环还是继续循环。
while、do...while、for 能够循环数组,如果要迭代对象则需要结合 Object.getOwnPropertyNames(不包括 symbol 类型)。for...in 能够循环对象(包括数组),但是它会循环对象的所有可枚举属性,包括原型链上的属性。因此,通常需要结合 hasOwnProperty 对象实例方法来过滤。
ECMAScript 2015 引入 Object.keys 返回对象 key 的数组。ECMAScript 2017 引入 Object.values 返回对象 value 的数组,Object.entries 返回对象的键值对数组。它们都是是对象自身的(不含继承的)所有可遍历(enumerable)属性的键或值,但不包括 Symbol 属性。
与之配套的是 Object.getOwnPropertySymbols,返回包含对象自身的所有 Symbol 属性键名的数组。还可以使用 Reflect.ownKeys,返回包含对象自身的(不含继承的)所有键名的数组,不管键名是 Symbol 或字符串,也不管是否可枚举。
判断对象的属性是自身的属性和继承的属性,除了 hasOwnProperty 对象实例方法,ECMAScript 2022 引入 Object.hasOwn,判断是否为自身的属性。Object.hasOwn 的一个好处是,对于不继承 Object.prototype 的对象不会报错,而 hasOwnProperty 是会报错的。
除了数组和对象,ECMAScript 2015+ 添加了新的数据结构 Map、Set。为了处理所有不同数据结构,就需要一种统一的接口机制。遍历器(Iterator)就是这样一种机制,它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
Iterator 的作用有三个:
- 为各种数据结构,提供一个统一的、简便的访问接口。
- 使得数据结构的成员能够按某种次序排列。
- 创造了一种新的遍历命令
for...of循环,Iterator接口供for...of消费。
迭代器

在 JavaScript 中,迭代器是一个对象,它定义一个序列,并在终止时可能返回一个返回值。具体来说,迭代器是通过具有 next() 方法来实现 Iterator 协议的任何对象,该方法返回具有两个属性的对象:
value:迭代序列中的next值。done:如果已经迭代到序列中的最后一个值,则为true。如果value与done一起出现,它则是迭代器的返回值。
创建迭代器对象后,可以通过重复调用 next() 来显式迭代。对迭代器进行迭代被称为消耗迭代器,因为它通常只能执行一次。产生终止值后,对 next() 的其他调用应继续返回 {done: true} 。
生成器函数

虽然自定义迭代器是一个有用的工具,但由于需要显式维护其内部状态,因此它们的创建需要仔细编程。生成器函数提供了一个强大的替代方案:它们允许您通过编写执行不连续的单个函数来定义迭代算法。生成器函数使用 function* 语法编写。
调用时,生成器函数最初并不执行其代码。相反,它们返回一种特殊类型的迭代器,称为生成器。当通过调用生成器的 next 方法使用某个值时,生成器函数将执行直到遇到 yield 关键字。该函数可以根据需要多次调用,并且每次都会返回一个新的生成器。每个生成器只能迭代一次。
迭代协议
迭代协议不是新的内置函数或语法,而是协议。这些协议可以由任何对象通过遵循一些约定来实现。迭代协议具体分为两个协议有两种协议:可迭代协议和迭代器协议。
可迭代协议
可迭代协议(iterable protocol)允许 JavaScript 对象定义或自定义其迭代行为,例如在 for...of 构造中循环哪些值。某些内置类型是具有默认迭代行为的内置可迭代对象,例如 Array 或 Map ,而其他类型则不是。
为了可迭代,对象必须实现 @@iterator 方法,这意味着该对象(或者它原型链上的某个对象)必须具有带有 @@iterator 键的属性,该属性可通过常量 Symbol.iterator 获得:
[Symbol.iterator]:一个无参数的函数,其返回值为一个符合迭代器协议的对象。
每当需要迭代对象时,都会不带参数调用其 @@iterator 方法,并使用此方法返回的迭代器获得要迭代的值。值得注意的是调用此无参数函数时,它将作为对可迭代对象的方法进行调用。因此,在函数内部, this 关键字可用于访问可迭代对象的属性,以决定在迭代期间提供什么。
该函数可以是普通函数,也可以是生成器函数,以便在调用时返回迭代器对象。在此生成器函数内部,可以使用 yield 提供每个条目。
迭代器协议
迭代器协议(iterator protocol)定义了一种生成值序列(有限或无限)的标准方法,并且在生成所有值时可能会返回一个返回值。当对象实现具有以下语义的 next() 方法时,该对象就是迭代器:
next():无参数或者接受一个参数的函数,并返回符合IteratorResult接口的对象。如果在使用迭代器内置的语言特征,得到一个非对象返回值,将会抛出TypeError("iterator.next() returned a non-object value")。
所有迭代器协议方法(next()、return() 和 throw())都应返回实现 IteratorResult 接口的对象。它必须具有以下属性:
done:如果迭代器能够生成序列中的下一个值,则为false的布尔值。如果迭代器已完成其序列,则具有值true。在这种情况下,value可以选择指定迭代器的返回值。value:迭代器返回的任何 JavaScript 值。当done为 true 时可以省略。
实际上,这两种属性都不是严格要求的。如果返回没有任一属性的对象,则它实际上相当于 { done: false, value: undefined }。如果迭代器返回 done: true 的结果,则对 next() 的任何后续调用也预计会返回 done: true,尽管这在语言级别上不强制执行。
next 方法可以接受一个值,该值将提供给方法体。任何内置的语言特征都将不会传递任何值。传递给生成器 next 方法的值将成为相应 yield 表达式的值。或者,迭代器还可以实现 return(value) 和 throw(exception) 方法,这些方法在被调用时告诉迭代器调用者已完成迭代,并且可以执行任何必要的清理。
return(value):无参数或者接受一个参数的函数并返回符合IteratorResult接口的对象,通常value等于传入的value和done。调用此方法告诉迭代器调用者不打算进行任何更多的next()调用,并且可以执行任何清理操作。throw(exception):无参数或者接受一个参数的函数,并返回符合IteratorResult接口的对象,通常done等于true。调用此方法告诉迭代器调用者检测到错误条件,并且exception通常是Error实例。
异步迭代器和异步可迭代协议
还有另一对用于异步迭代的协议,称为异步迭代器(async iterator)和异步可迭代协议(async iterable protocols)。与可迭代和迭代器协议相比,它们具有非常相似的接口,只是调用迭代器方法的每个返回值都包装在一个 Promise 中。当对象实现以下方法时,它就实现了异步可迭代协议:
[Symbol.asyncIterator]:返回对象的无参数函数,并且符合异步迭代器协议。
当对象实现以下方法时,它就实现了异步迭代器协议:
next():无参数或者接受一个参数的函数,并返回promise。Promise会满足一个符合IteratorResult接口的对象,并且属性与同步迭代器的属性具有相同的语义。return(value):无参数或者接受一个参数的函数,并返回promise。Promise会满足一个符合IteratorResult接口的对象,并且属性与同步迭代器的属性具有相同的语义。throw(exception):无参数或者接受一个参数的函数,并返回promise``。Promise会满足一个符合IteratorResult接口的对象,并且属性与同步迭代器的属性具有相同的语义。
语言和迭代协议之间的交互
String、Array、TypedArray、Map、Set 以及 Intl.Segments 都是内置的可迭代对象,因为它们的每个 prototype 对象都实现了 @@iterator 方法。此外,arguments 对象和一些 DOM 集合类型,如 NodeList 也是可迭代的。ReadableStream 是唯一的内置异步可迭代对象。
生成器函数返回生成器对象,它们是可迭代的迭代器。异步生成器函数返回异步生成器对象,它们是异步可迭代迭代器。
从内置迭代器返回的迭代器实际上都继承自一个公共类 Iterator,该类实现了 [Symbol.iterator]() { return this; } 方法,使它们都是可迭代迭代器。
有许多 API 接受可迭代对象,包括:Map()、WeakMap()、Set()、WeakSet()、Promise.all()、Promise.allSettled()、Promise.race()、Promise.any()、Array.from()、Object.groupBy()、Map.groupBy()。