出处:MDN
原作者:MDN
迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现
迭代协议具体分为两个协议:[[#可迭代协议]]和[[#迭代器协议]]
![[Pasted image 20250729165309.png]]
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of 结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array、Set、Map 等,而其他内置类型则不是(比如 Object)
要成为可迭代对象,该对象必须实现 [Symbol.iterator]() 方法,这意味着对象(或者它原型链上的某个对象)必须有一个键为 [Symbol.iterator] 的属性:
[Symbol.iterator] 是一个无参数的函数,其返回值为一个符合[[#迭代器协议]]的对象
当一个对象需要被迭代的时候(例如 for..of),首先,会不带参数调用它的 [Symbol.iterator]() 方法,然后使用此方法返回的迭代器获得要迭代的值
值得注意的是调用此无参数函数时,它将作为对可迭代对象的方法进行调用。因此,在函数内部,this 关键字可用于访问可迭代对象的属性,以决定在迭代过程中提供什么
此函数可以是普通函数,也可以是 [[010.Generator 函数|Generator 函数]],以便在调用时返回迭代器对象。在此生成器函数的内部,可以使用 yield 提供每个条目
迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式,当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值
只有实现了一个拥有以下语义(semantic)的 next() 方法,一个对象才能成为迭代器:
next()无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象(见下文)。如果在使用迭代器内置的语言特征(例如 for...of)时,得到一个非对象返回值(例如 false 或 undefined),将会抛出 TypeError:"iterator.next() returned a non-object value"
所有迭代器协议的方法(next()、return() 和 throw())都应返回实现 IteratorResult 接口的对象。它必须有以下属性:
done(可选)如果迭代器能够生成序列中的下一个值,则返回 false 布尔值(这等价于没有指定 done 这个属性)
如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值
value(可选)迭代器返回的任何 JavaScript 值。done 为 true 时可省略
说明:
{ done: false, value: undefined }done: true 的结果,则对任何 next() 的后续调用也返回 done: true,尽管这在语言层面不是强制的next 方法可以接受一个值,该值将提供给方法体,详见:[[010.Generator 函数|Generator 函数]]可选地,迭代器也实现了 return(value) 和 throw(exception) 方法,这些方法在调用时告诉迭代器,调用者已经完成迭代,并且可以执行任何必要的清理(例如关闭数据库连接)
return(value)(可选)无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象,其 value 通常等价于传递的 value,并且 done 等于 true。调用这个方法表明迭代器的调用者不打算调用更多的 next(),并且可以进行清理工作
throw(exception)(可选)无参数或者接受一个参数的函数,并返回符合 IteratorResult 接口的对象,通常 done 等于 true。调用这个方法表明迭代器的调用者监测到错误的状况,并且 exception 通常是一个 Error 实例
无法通过反射的方法确定(例如,没有实际调用 next() 并验证返回的结果)一个特定的对象是否实现了迭代器协议
很容易使一个迭代器也可迭代:只需实现 [Symbol.iterator]() 方法,并返回它的 this
// 满足 Iterator 和 Iterable 协议
const myIterator = {
next() {
// ...
},
[Symbol.iterator]() {
return this;
},
};
这种对象被称为可迭代迭代器。这样做可以让期望可迭代对象的各类语法使用此类迭代器——因此,在不实现可迭代协议的情况下,仅实现迭代器协议的作用很小。(事实上,几乎所有的语法和 API 都期待可迭代对象,而不是迭代器)[[010.Generator 函数|Generator 对象]]是一个例子:
const aGeneratorObject = (function* () {
yield 1;
yield 2;
yield 3;
})();
console.log(typeof aGeneratorObject.next);
// "function" —— 它有 next 方法(返回正确的值),所以它是迭代器
console.log(typeof aGeneratorObject[Symbol.iterator]);
// "function" —— 它有 [Symbol.iterator]() 方法(返回正确的迭代器),所以它是可迭代的
console.log(aGeneratorObject[Symbol.iterator]() === aGeneratorObject);
// true —— 它的 [Symbol.iterator]() 方法返回自身(一个迭代器),所以它是一个可迭代的迭代器
然而,可能的情况下,iterable[Symbol.iterator] 最好返回总是从头开始的不同的迭代器,像 Set.prototype[Symbol.iterator]() 做的那样。而不是上例这样共用一个迭代器
还有一对用于异步迭代的协议,命名为异步迭代器和异步可迭代协议。它们与可迭代和迭代器协议有着非常类似的接口,只是从调用迭代器方法的每个返回值都包装在一个 promise 中
当对象实现以下方法时,它会实现异步可迭代协议:
[Symbol.asyncIterator]返回对象的无参数函数,并且符合异步迭代器协议
当对象实现以下方法时,它会实现异步迭代器协议:
next()无参数或者接受一个参数的函数,并返回 promise。promise 兑现为一个对象,该对象符合 IteratorResult 接口,并且这些属性与同步迭代器有着相同的语义
return(value)(可选)无参数或者接受一个参数的函数,并返回 promise。promise 兑现为一个对象,该对象符合 IteratorResult 接口,并且这些属性与同步迭代器有着相同的语义
throw(exception)(可选)无参数或者接受一个参数的函数,并返回 promise。promise 兑现为一个对象,该对象符合 IteratorResult 接口,并且这些属性与同步迭代器有着相同的语义
JavaScript 语言指定了产生或使用可迭代对象和迭代器的 API
String、Array、[[012.二进制数据#类型化数组(TypedArray)|TypedArray]]、Map、Set 以及 Intl.Segments 都是内置的可迭代对象,因为它们的每个 prototype 对象都实现了 [Symbol.iterator]() 方法。此外,arguments 对象和一些 DOM 集合类型,如 NodeList 也是可迭代的
目前,没有内置的异步可迭代对象
[[010.Generator 函数|Generator 函数]]返回生成器对象,它们是可迭代的迭代器。异步生成器函数返回异步生成器对象,它们是异步可迭代的迭代器
从内置迭代返回的迭代器实际上都继承了一个公共类(目前尚未暴露),该类实现了上述 [Symbol.iterator]() { return this; } 方法,使它们都是可迭代的迭代器。将来,除了迭代器协议要求的 next() 方法外,这些内置迭代器可能还有其他辅助方法。你可以通过在图形控制台中记录迭代器的原型链来检查它
console.log([][Symbol.iterator]());
Array Iterator {}
[[Prototype]]: Array Iterator ==> 这是所有数组迭代器共享的原型
next: ƒ next()
Symbol(Symbol.toStringTag): "Array Iterator"
[[Prototype]]: Object ==> 这是所有内置迭代器共享的原型
Symbol(Symbol.iterator): ƒ [Symbol.iterator]()
[[Prototype]]: Object ==> 这是 Object.prototype
有许多 API 接受可迭代对象。一些例子,包括:
Map()WeakMap()Set()WeakSet()Promise.all()Promise.allSettled()Promise.race()Promise.any()Array.from()Object.groupBy()Map.groupBy()const myObj = {};
new WeakSet(
(function* () {
yield {};
yield myObj;
yield {};
})(),
).has(myObj); // true
一些语句和表达式期望可迭代对象,例如:
for...of 循环[...iter]yield*:[[010.Generator 函数#委托生成器 (yield*)|Generator 委托生成器]][a, b, c] = iterfor (const value of ["a", "b", "c"]) {
console.log(value);
}
// "a"
// "b"
// "c"
console.log([..."abc"]); // ["a", "b", "c"]
function* gen() {
yield* ["a", "b", "c"];
}
console.log(gen().next()); // { value: "a", done: false }
console.log(gen().next()); // { value: "b", done: false }
console.log(gen().next()); // { value: "c", done: false }
console.log(gen().next()); // { value: undefined, done: true }
[a, b, c] = new Set(["a", "b", "c"]);
console.log(a); // "a"
当内置语法迭代迭代器,并且最后的结果中 done 为 false(即迭代器能够生成更多值)但不需要更多值时,如果存在,将调用 return 方法。例如,如果在 for...of 循环中遇到 break 或 return,或者数组解构中的标识符只是有限个的,则可能会发生这种情况:
const obj = {
[Symbol.iterator]() {
let i = 0;
return {
next() {
i++;
console.log("Returning", i);
if (i === 3) return { done: true, value: i };
return { done: false, value: i };
},
return() {
console.log("Closing");
return { done: true };
},
};
},
};
const [b] = obj;
// Returning 1
// Closing
const [a, b, c] = obj;
// Returning 1
// Returning 2
// Returning 3
// 已经达到终点(最后的调用返回了 done: true),
// 所以 return() 不会被调用
for (const b of obj) {
break;
}
// Returning 1
// Closing
异步生成器函数 中的 for await...of 循环和 yield* 是与异步迭代交互的唯一方式。在不是同步迭代的异步迭代对象(即它有 [Symbol.asyncIterator]() 但没有 [Symbol.iterator]())上使用 for...of、数组展开等将抛出 TypeError:x is not iterable
如果一个可迭代对象的 [Symbol.iterator]() 方法不能返回迭代器对象,那么可以认为它是一个不符合标准的(Non-well-formed)可迭代对象
使用这样的可迭代对象很可能会导致如下的运行时异常,或者不可预料的表现:
const nonWellFormedIterable = {};
nonWellFormedIterable[Symbol.iterator] = () => 1;
[...nonWellFormedIterable]; // TypeError: [Symbol.iterator]() returned a non-object value
你可以像这样创建自己的可迭代对象:
const myIterable = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
},
};
console.log([...myIterable]); // [1, 2, 3]
迭代器本质上是有状态的,如果你不将其定义为 Generator 函数(如上例所示),你可能需要将状态封装在闭包中
function makeIterator(array) {
let nextIndex = 0;
return {
next() {
return nextIndex < array.length
? {
value: array[nextIndex++],
done: false,
}
: {
done: true,
};
},
};
}
const it = makeIterator(["yo", "ya"]);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done); // true
function idMaker() {
let index = 0;
return {
next() {
return {
value: index++,
done: false,
};
},
};
}
const it = idMaker();
console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// ...
function* makeSimpleGenerator(array) {
let nextIndex = 0;
while (nextIndex < array.length) {
yield array[nextIndex++];
}
}
const gen = makeSimpleGenerator(["yo", "ya"]);
console.log(gen.next().value); // 'yo'
console.log(gen.next().value); // 'ya'
console.log(gen.next().done); // true
function* idMaker() {
let index = 0;
while (true) {
yield index++;
}
}
const it = idMaker();
console.log(it.next().value); // 0
console.log(it.next().value); // 1
console.log(it.next().value); // 2
// ...
class SimpleClass {
#data;
constructor(data) {
this.#data = data;
}
[Symbol.iterator]() {
// 为每个迭代器使用一个新的索引
let index = 0;
return {
// 注意:使用箭头函数允许 this 指向 [Symbol.iterator](),而不是 next()
next: () => {
if (index < this.#data.length) {
return { value: this.#data[index++], done: false };
} else {
return { done: true };
}
},
};
}
}
const simple = new SimpleClass([1, 2, 3, 4, 5]);
for (const val of simple) {
console.log(val); // 1 2 3 4 5
}