第12章 迭代器和生成器
ES6新概念,迭代器和生成器。
生成器依赖于迭代器。
迭代器可以粗略的比作书签:它可以帮助用户追踪当前的位置。
对于数组,可用通过数组的 values() 方法获取迭代器。
next 方法返回的对象的两个
属性:value(保存当前的值)和done。
const arr = ["a","b","c","d"];
const it = arr.values();
it.next();
//
{value: "a", done: false}
it.next(); //
{value: "b", done: false}
it.next(); //
{value: "c", done: false}
it.next(); //
{value: "d", done: false}
it.next(); //
{value: undefined, done: true}
for...of 循环实际上使用了迭代器:for...of 循环对任何可以提供迭代器的结构都适用。
在while 循环中使用迭代器 来模拟一个 for...of 循环:
const arr = ["a","b","c","d"];
const it = arr.values();
let current = it.next();
while(!current.done){
console.log(current.value);
current = it.next();
}
//a
//b
//c
//d
注意,迭代器是相互独立的。
每当创建一个新的迭代器时,就会从头开始,这样就可以在不同的地方使用多个迭代器了:
const arr = ["a","b","c","d"];
const it1 = arr.values();
const it2 = arr.values();
it1.next(); //
{value: "a", done: false}
it1.next(); //
{value: "b", done: false}
it2.next(); //
{value: "a", done: false}
it1.next(); //
{value: "c", done: false}
12.1 迭代协议
迭代器协议让任何对象变得可迭代。
创建一个Log 类,使用一个数组来存储时间戳消息,把时间戳作为附加消息:
class Log{
constructor(){
this.messages = [];
}
add(message){
this.messagess.push({ message, timestamp: Date.now() });
}
}
迭代器协议:如果一个类提供了一个符号方法 Symbol.iterator,这个方法返回一个具有迭代行为的对象。
修改 Log 类,给它添加Symbol.iterator 方法:
class Log{
constructor(){ this.messages = []; }
add(message){
this.messages.push({ message, timestamp: Date.now() });
}
[Symbol.iterator](){
return this.messages.values();
}
}
//下面就可以像数组那样迭代 Log 类的实例了:
const log = new Log();
log.add("first");
log.add("second");
log.add("third");
//像数组一样迭代
for(let entry of log){
console.log(`${entry.message} @ ${entry.timestamp}`);
}
//
first @ 1527727563279
//second @ 1527727563279
//third @ 1527727563279
上面例子,通过从messages 数组中取出一个迭代器的方式保持迭代器协议,当然也可以编写自己的迭代器:
class Log{
constructor(){ this.messages = []; }
add(message){
this.messages.push(
{ message, timestamp: Date.now() });
}
[Symbol.iterator](){
let i = 0;
const messages = this.messages;
return{
next(){
if(i >= messages.length) return{ value: undefined, done: true};
return{ value: messages[i++],done: false};
}
}
}
}
12.2 生成器
生成器(Generator)是使用迭代器来控制其运行的函数。
生成器的两种能力:
-
控制函数执行的能力,使函数能够分布执行;
-
与执行中的函数对话的能力。
生成器与一般的函数有两个区别:
-
函数可以通过使用 yield,在函数运行的任意时刻将控制权交还给调用方。
-
调用生成器的时候,它并不是立即执行。而是会回到迭代器中。函数会在调用迭代器的next 方法时执行。
生成器需要在function 关键字后面添加一个通配符( * )来指明;然后就可以在 return 中添加 yield 关键字了。
一个返回彩虹中所有颜色的生成器:
function* rainbow(){
yield "red";
yield "orange";
yield "yellow";
yield "green";
yield "blue";
yield "indigo";
yield "violet";
}
//然后调用这个生成器
//记住,当调用生成器时,实际上是回到了迭代器中
const it = rainbow();
it.next(); //
{value: "red", done: false}
it.next(); //
{value: "orange", done: false}
//...
//因为rainbow 生成器返回了一个迭代器,所以也可以使用 for...of 循环
for(let color of rainbow()){
console.log(color);
} //将会把所有颜色输出到控制台
12.2.1 yield 表达式和双向交流
生成器可以让它和其调用者进行双向交流,这个功能是通过yield 表达式实现的。
yield 是表达式,所以也能够计算出一个值。它计算的是调用方每次在生成器的迭代器上调用 next 时提供的参数。
下面是一个可以进行对话的生成器:
function* interrogate(){
const name = yield "What is your name?";
const color = yield "What is your favorite color?";
return `${name}'s favorite color is ${color}.`;
}
//让it 调用生成器,得到一个迭代器,然后可以使用next方法了
const it = interrogate();
it.next(); //
{value: "What is your name?", done: false}
it.next("liang"); //
{value: "What is your favorite color?", done: false}
it.next("white"); //{value: "liang's favorite color is white.", done: true}
it.next(); //{value: undefined, done: true}
当第一次调用next时,会先运行第一行。由于第一行包含yield 表达式,生成器的控制权回到调用方 it。调用方必须在第一行被执行前调用 next,此时name 就会接收一个准备传入给 next的值。
12.2.2 生成器和返回值
yield 表达式不能让生成器结束。
在生成器的任意位置调用 return 都会使 done 的值变为 true,而 value 的值则是任何被返回的值。
function* abc(){
yield "a";
yield "b";
return "c";
}
const it = abc();
it.next(); //
{value: "a, done: false}
it.next();
//
{value: "b", done: false}
it.next(); //
//
{value: "c", done: true}
for(let i of abc()){
console.log(i);
}
//a
//b
c 在 for...of 循环中没有被打印。建议不要在 return 中提供一个对生成器有意义的值。return 应该只被用来提前停止生成器。
建议:在生成器中调用 return 的时候,不提供返回值。
12.3 小结
迭代器为集合或对象这类可以提供多个值的数据类型提供了一个标准的方式。
生成器使得函数更易于控制和定制化:函数调用方不再局限于提供数据后等待函数返回,从而获取函数返回值。生成器实际上是将计算延迟了,只在需要的时候进行。
将在第14章看到生成器是如何提供用于管理异步执行的强大模式。