0%

学习笔记 2020 10 16

学习笔记 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 关键字只能直接位于生成器函数内部,不能出现在嵌套的非生成器函数中。

  1. 生成器函数作为可迭代对象
    function* generatorFn() {
      yield 1;
      yield 2;
      yield 3;
    }
    for (const x of generatorFn()) {
      console.log(x);
    }
    // 1
    // 2
    // 3
  2. 使用 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 方法的第一个值。

    但第一次调用传入的值不会被使用,这一次是为了开始执行生成器函数。

  3. 产生可迭代对象

    可以使用星号增强 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 属性。

  4. 使用 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] }

returnthrow 都强制生成器进入关闭状态。

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}