《每天十分钟》-红宝书第4版-迭代器与生成器(二)

生成器

基础知识

生成器的形式是一个函数,这个函数比较特殊,它拥有在一个函数块内暂停和恢复代码执行的能力。
函数名称前面加一个星号(*) 这就表示它是一个生成器了

// 生成器函数声明
function* generatorFn() {} 
// 生成器函数表达式
let generatorFn = function* () {} 
// 作为对象字面量方法的生成器函数
let foo = { 
 * generatorFn() {} 
} 
// 作为类实例方法的生成器函数
class Foo { 
 * generatorFn() {} 
} 
// 作为类静态方法的生成器函数
class Bar { 
 static * generatorFn() {} 
}

标识生成器函数的星号不受两侧空格的影响

调用生成器函数会产生一个生成器对象。生成器对象一开始处于暂停执行(suspended)的状态。与
迭代器相似,生成器对象也实现了 Iterator 接口,因此具有 next()方法。调用这个方法会让生成器
开始或恢复执行。

function* generatorFn() {} 
const g = generatorFn(); 
console.log(g); // generatorFn {} 
console.log(g.next); // f next() { [native code] }
// 函数体为空的生成器函数中间不会停留,调用一次 next()就会让生成器到达 done: true 状态
console.log(g.next()); // { done: true, value: undefined }

value 属性是生成器函数的返回值,默认值为 undefined,可以通过生成器函数的返回值指定:

function* generatorFn() {
 return 'foo'; 
} 
let generatorObject = generatorFn(); 
console.log(generatorObject); // generatorFn {} 
console.log(generatorObject.next()); // { done: true, value: 'foo' }

通过 yield 中断执行

function* generatorFn() { 
 yield 'foo'; 
 yield 'bar'; 
 return 'baz'; 
} 
let generatorObject = generatorFn(); 
let generatorObject2 = generatorFn();
console.log(generatorObject.next()); // { done: false, value: 'foo' } 
console.log(generatorObject.next()); // { done: false, value: 'bar' } 
console.log(generatorObject.next()); // { done: true, value: 'baz' }
//生成器函数内部的执行流程会针对每个生成器对象区分作用域。在一个生成器对象上调用 next()不会影响其他生成器
console.log(generatorObject2.next()); // { done: false, value: 'foo' }

使用场景

在生成器对象上显式调用 next()方法的用处并不大。那使用生成器适用哪些场景

生成器对象作为可迭代对象

function* generatorFn() { 
 yield 1; 
 yield 2; 
 yield 3; 
} 
// 这里就是遍历可迭代对象
for (const x of generatorFn()) { 
 console.log(x); 
} 
// 1 
// 2 
// 3

实现输入输出

//因为函数必须对整个表达式求值才能确定要返回的值,所以它在遇到 yield 关键字时暂停执行并计算出要产生的值:"foo"。下一次调用 next()传入了"bar",作为交给同一个 yield 的值。然后这个值被确定为本次生成器函数要返回的值
function* generatorFn() { 
 return yield 'foo'; 
} 
let generatorObject = generatorFn(); 
console.log(generatorObject.next()); // { done: false, value: 'foo' } 
console.log(generatorObject.next('bar')); // { done: true, value: 'bar' }

yield 关键字并非只能使用一次。比如,以下代码就定义了一个无穷计数生成器函数:

function* generatorFn() { 
 for (let i = 0;;++i) { 
 yield i; 
 } 
} 
let generatorObject = generatorFn(); 
console.log(generatorObject.next().value); // 0 
console.log(generatorObject.next().value); // 1 
console.log(generatorObject.next().value); // 2 
console.log(generatorObject.next().value); // 3 
console.log(generatorObject.next().value); // 4 
console.log(generatorObject.next().value); // 5

有限计数

function* nTimes(n) { 
 for (let i = 0; i < n; ++i) { 
 yield i; 
 } 
} 
for (let x of nTimes(3)) { 
 console.log(x); 
} 
// 0 
// 1 
// 2

产生可迭代对象

使用星号增强 yield

// 等价的 generatorFn: 
// function* generatorFn() { 
// for (const x of [1, 2, 3]) { 
// yield x; 
// } 
// } 
function* generatorFn() { 
 yield* [1, 2, 3]; 
} 
let generatorObject = generatorFn(); 
for (const x of generatorFn()) { 
 console.log(x); 
} 
// 1 
// 2 
// 3

因为 yield*实际上只是将一个可迭代对象序列化为一连串可以单独产出的值,所以这跟把 yield放到一个循环里没什么不同

使用 yield*实现递归算法

function* nTimes(n) { 
 if (n > 0) { 
 yield* nTimes(n - 1); 
 yield n - 1; 
 } 
} 
for (const x of nTimes(3)) { 
 console.log(x); 
} 
// 0 
// 1 
// 2

生成器作为默认迭代器

class Foo { 
 constructor() { 
 this.values = [1, 2, 3]; 
 }
 * [Symbol.iterator]() { 
 yield* this.values; 
 } 
} 
const f = new Foo(); 
for (const x of f) { 
 console.log(x); 
} 
// 1 
// 2 
// 3

for-of 循环调用了默认迭代器(它恰好又是一个生成器函数)并产生了一个生成器对象。
这个生成器对象是可迭代的,所以完全可以在迭代中使用。

提前终止生成器

与迭代器类似,生成器也支持“可关闭”的概念。一个实现 Iterator 接口的对象一定有 next()方法,还有一个可选的 return()方法用于提前终止迭代器。生成器对象除了有这两个方法,还有第三个方法:throw()。

function* generatorFn() {} 
const g = generatorFn(); 
console.log(g); // generatorFn {} 
console.log(g.next); // f next() { [native code] } 
console.log(g.return); // f return() { [native code] } 
console.log(g.throw); // f throw() { [native code] }

小结

迭代是一种所有编程语言中都可以看到的模式。ECMAScript 6 正式支持迭代模式并引入了两个新的语言特性:迭代器和生成器。

迭代器必须通过连续调用 next()方法才能连续取得值,这个方法返回一个 IteratorObject。这个对象包含一个 done 属性和一个 value 属性。前者是一个布尔值,表示是否还有更多值可以访问;后者包含迭代器返回的当前值。这个接口可以通过手动反复调用 next()方法来消费,也可以通过原生消费者,比如 for-of 循环来自动消费。

生成器是一种特殊的函数,调用之后会返回一个生成器对象。生成器对象实现了 Iterable 接口,因此可用在任何消费可迭代对象的地方。生成器的独特之处在于支持 yield 关键字,这个关键字能够暂停执行生成器函数。使用 yield 关键字还可以通过 next()方法接收输入和产生输出。在加上星号之后,yield 关键字可以将跟在它后面的可迭代对象序列化为一连串值。

劝学 唐 孟郊
击石乃有火,不击元无烟。
人学始知道,不学非自然。
万事须己运,他得非我贤。
青春须早为,岂能长少年。

你可能感兴趣的:(《每天十分钟》红宝书第4版,前端,javascript)