学习笔记 2020-10-16
JavaScript 高级程序设计(第4版) 阅读记录
迭代器与生成器
生成器
yield
关键字可以让生成器停止和开始执行。
生成器函数在遇到 yield
关键字之前会正常执行。遇到之后,执行会停止,保留函数作用域的状态。
在生成器对象上调用 next()
方法来恢复执行。
function* generatorFn() {
yield 'foo';
yield 'bar';
return 'baz';
}
let generatorObject = 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' }
通过 yield
关键字退出的生成器函数会处于 done: false
状态,通过 return
关键字退出的生成器函数会处于 done: true
状态。
生成器函数内部会针对每个生成器对象区分作用域。
yield
关键字只能直接位于生成器函数内部,不能出现在嵌套的非生成器函数中。
生成器函数作为可迭代对象
function* generatorFn() { yield 1; yield 2; yield 3; } for (const x of generatorFn()) { console.log(x); } // 1 // 2 // 3
使用 yield 实现输入和输出
yield
可以作为函数的中间参数使用。function* generatorFn(initial) { console.log(initial); console.log(yield); console.log(yield); } let generatorObject = generatorFn('foo'); generatorObject.next('bar'); // foo generatorObject.next('baz'); // baz generatorObject.next('qux'); // qux
上一次让生成器函数暂停的
yield
关键字会接收到传给next
方法的第一个值。但第一次调用传入的值不会被使用,这一次是为了开始执行生成器函数。
产生可迭代对象
可以使用星号增强
yield
的行为,让它迭代一个可迭代对象。function* generatorFn() { yield* [1, 2, 3]; } let generatorObject = generatorFn(); for (const x of generatorFn()) { console.log(x); } // 1 // 2 // 3
yield*
的值是关联迭代器返回done: true
时的value
属性。使用 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 Node { constructor(id) { this.id = id; this.neighbors = new Set(); } connect(node) { if (node !== this) { this.neighbors.add(node); node.neighbors.add(this); } } } class RandomGraph { constructor(size) { this.nodes = new Set(); // 创建节点 for (let i = 0; i < size; ++i) { this.nodes.add(new Node(i)); } // 随机连接节点 const threshold = 1 / size; for (const x of this.nodes) { for (const y of this.nodes) { if (Math.random() < threshold) { x.connect(y); } } } } // 这个方法仅用于调试 print() { for (const node of this.nodes) { const ids = [...node.neighbors].map(n => n.id).join(','); console.log(`${node.id}: ${ids}`); } } isConnected() { const visitedNodes = new Set(); function* traverse(nodes) { for (const node of nodes) { if (!visitedNodes.has(node)) { yield node; yield* traverse(node.neighbors); } } } // 取得集合中的第一个节点 const firstNode = this.nodes[Symbol.iterator]().next().value; // 使用递归生成器迭代每个节点 for (const node of traverse([firstNode])) { visitedNodes.add(node); } return visitedNodes.size === this.nodes.size; } } const g = new RandomGraph(6); g.print(); // 示例输出: // 0: 2,3,5 // 1: 2,3,4,5 // 2: 1,3 // 3: 0,1,2,4 // 4: 2,3 // 5: 0,4
生成器作为默认迭代器
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
提前终止生成器
一个实现 Iterator
接口的对象一定有 next()
方法,还有一个可选的 return()
方法用于提前终止迭代器。生成器对象还有第三个方法,throw()
。
function* generatorFn() {}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.next); // f next() { [native code] }
console.log(g.return); // f return() { [native code] }
console.log(g.throw); // f throw() { [native code] }
return
和 throw
都强制生成器进入关闭状态。
throw
方法会在暂停的时候将一个提供的错误注入到生成器对象。如果错误未被处理,生成器就会被关闭。
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;
}
}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
try {
g.throw('foo');
} catch (e) {
console.log(e); // foo
}
console.log(g); // generatorFn {<closed>}
如果生成器函数内部处理了错误,生成器就不会被关闭,可以恢复执行。错误处理会跳过对应的 yield
。
function* generatorFn() {
for (const x of [1, 2, 3]) {
try {
yield x;
} catch (e) {}
}
}
const g = generatorFn();
console.log(g.next()); // { done: false, value: 1}
g.throw('foo');
console.log(g.next()); // { done: false, value: 3}