出处:MDN
原作者:MDN
迭代协议并不是新的内置实现或语法,而是协议。这些协议可以被任何遵循某些约定的对象来实现
可迭代协议
可迭代协议允许 JavaScript 对象定义或定制它们的迭代行为,例如,在一个 for..of
结构中,哪些值可以被遍历到。一些内置类型同时是内置的可迭代对象,并且有默认的迭代行为,比如 Array
、Set
、Map
等,而其他内置类型则不是(比如 Object
)
要成为可迭代对象,该对象必须实现 [Symbol.iterator]()
方法,这意味着对象(或者它原型链上的某个对象)必须有一个键为 [Symbol.iterator]
的属性:
[Symbol.iterator]
是一个无参数的函数,其返回值为一个符合迭代器协议的对象
当一个对象需要被迭代的时候(例如 for..of
),首先,会不带参数调用它的 [Symbol.iterator]()
方法,然后使用此方法返回的迭代器获得要迭代的值
值得注意的是调用此无参数函数时,它将作为对可迭代对象的方法进行调用。因此,在函数内部,this
关键字可用于访问可迭代对象的属性,以决定在迭代过程中提供什么
此函数可以是普通函数,也可以是 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
方法可以接受一个值,该值将提供给方法体,详见: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 都期待可迭代对象,而不是迭代器)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
、TypedArray、Map
、Set
以及 Intl.Segments
都是内置的可迭代对象,因为它们的每个 prototype
对象都实现了 [Symbol.iterator]()
方法。此外,arguments
对象和一些 DOM 集合类型,如 NodeList
也是可迭代的
目前,没有内置的异步可迭代对象
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
有许多 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*
:Generator 委托生成器- 数组解构:
[a, b, c] = iter
for (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
}