-
Notifications
You must be signed in to change notification settings - Fork 20
Description
概述
我们都使用过for...of语句,并大部分时候用在Array上,不仅如此,它还可以迭代String、Set、Map、函数的arguments对象、NodeList对象。
其实for...of是可以迭代任何可迭代对象(实现了可迭代协议的对象)的,下面将继续讲解。
1. 可迭代协议(The iterable protocol)
可迭代协议允许 JavaScript 对象去定义或定制它们的迭代行为, 例如(定义)在一个 for..of 结构中什么值可以被循环(得到)。
实现可迭代协议的对象叫做可迭代对象。
为了变成可迭代对象, 一个对象必须实现 @@iterator 方法,意思是这个对象(或者它原型链 prototype chain 上的某个对象)必须有一个名字是 Symbol.iterator 的属性。
Symbol.iterator 是一个函数,这个函数可以是生成器函数(执行后也会返回符合迭代器协议的对象),或者直接返回符合迭代器协议的对象。
比如String、Array、TypedArray、Map、WeakMap、Set 和 WeakSet 都是是实现了可迭代协议。
这里有一个String相关的例子:
let someString = "hi";
typeof someString[Symbol.iterator]; // "function"
let iterator = someString[Symbol.iterator](); // 实现了迭代器协议对象
iterator.next(); // { value: "h", done: false }
iterator.next(); // { value: "i", done: false }
iterator.next(); // { value: undefined, done: true }
当然我们也可以自定义一个实现了可迭代协议的对象:
// Symbol.iterator 是生成器函数
var myIterable = {};
myIterable[Symbol.iterator] = function* () {
yield 1;
yield 2;
yield 3;
};
[...myIterable]; // [1, 2, 3]
// 返回符合**迭代器协议**的对象
var someString = new String("hi");
someString[Symbol.iterator] = function() {
return { // 只返回一次元素,字符串 "bye",的迭代器对象
next: function() {
if (this._first) {
this._first = false;
return { value: "bye", done: false };
} else {
return { done: true };
}
},
_first: true
};
};
console.log([...someString]; // ["bye"]
实现了可迭代协议的对象的使用场景:
- for...of iterable
- 展开语法,[...iterable]
- 解构赋值,[a, b, ...rest] = iterable;
- Promise.all(iterable)
- Promise.race(iterable)
- Array.from(iterable)
- yield* iterable
2. 迭代器协议(The iterator protocol)
迭代器协议定义了一种标准的方式来产生一个有限或无限序列的值。
当一个对象被认为是一个迭代器时,它实现了一个 next() 的方法,并且该方法返回的对象里必须包含:
- done,boolean类型
- value,任何 JavaScript 值
3. 生成器
虽然自定义的迭代器是一个有用的工具,但由于需要显式地维护其内部状态,因此需要谨慎地创建。
Generators提供了一个强大的选择:它允许你定义一个包含自有迭代算法的函数, 同时它可以自动维护自己的状态。
如果使用function*语法,则函数将变为GeneratorFunction。
GeneratorFunction 是一个可以作为迭代器工厂的特殊函数。当它被执行时会返回一个新的生成器(iterator)对象。
如前面提到的迭代器协议所说,生成器对象有一个常用的API:next(),它会返回一个对象,value字段包含yield表达式返回的值,done字段表示生成器对象是否已经返回所有值。
在GeneratorFunction内部,yield关键字可以在每次生成器对象调用
next()的时候返回一次值。
yield ..和next(..)这一对组合起来,在生成器的执行过程中构成了一个双向消息传递系统。这也是可以使用生成器函数作为async/await的语法糖的原因。
由于生成器函数返回的生成器对象既是可迭代对象也是迭代器。所以生成器可以被for...of调用,也可以iterator.next().value调用每次的迭代值。
4. 结语
for...of语句,每次循环返回的是迭代器对象的value。形式如下:
for (let iterator.next().value of iterable) {}
生成器函数每次被调用返回的也是迭代器对象的value,只不过在函数内部用yield语句来表示迭代器对象的value。