JavaScript生成器简单介绍

立即通过http://jauyeung.net/subscribe/订阅我的电子邮件列表

在Twitter上关注我,网址为https://twitter.com/AuMayeung

https://medium.com/@hohanga上的更多文章

更多文章,请访问http://thewebdev.info/

在JavaScript中,生成器是返回生成器对象的特殊函数。生成器对象包含 next 可迭代对象的值。它用于让我们通过使用 for...of 环。这意味着返回的生成器函数符合可迭代的协议。

同步发电机

任何符合可迭代协议的内容都可以通过 for...of 环。像这样的对象 Array 要么 Map 遵守此协议。生成器功能也符合迭代器协议。这意味着它将以标准方式生成一系列值。它实现了 next 至少返回2个属性的函数- donevalue。的 done 属性是一个返回的布尔值 true 带有迭代器的对象已经超出了迭代序列的末尾。如果它可以产生序列中的下一个项目,则 falsevalue 是迭代器返回的项目。如果 donetrue 然后 value 可以省略。的 next 方法始终返回具有上述2个属性的对象。如果返回非对象值,则 TypeError 将被抛出。

要编写生成器函数,我们使用以下代码:

function* strGen() { 
  yield 'a';
  yield 'b';
  yield 'c';
}

const g = strGen();
for (let letter of g){
  console.log(letter)
}

function关键字后面的星号表示该函数是生成器函数。生成器函数将仅返回生成器对象。借助发电机功能, next 函数是自动生成的。发电机也有一个 return 函数返回给定值并结束生成器,并且 throw 函数引发错误并终止生成器,除非错误被生成器捕获。要从生成器返回下一个值,我们使用 yield 关键词。每次 yield 语句被调用,生成器被暂停直到 next 再次请求值。

执行上面的示例后,我们会记录“ a”,“ b”和“ c”,因为生成器在 for...of 环。每次运行下一次 yield 语句被调用,从列表中返回下一个值 yield 陈述。

我们还可以编写一个生成无限值的生成器。我们可以在生成器内部有一个无限循环,以保持返回新值。因为 yield 语句直到请求下一个值时才运行,我们可以保持无限循环运行而不会导致浏览器崩盘。例如,我们可以这样写:

function* genNum() {
  let index = 0;
  while(true){
    yield index += 2;
  }
}
const gen = genNum();
console.log(gen.next().value);
console.log(gen.next().value);
console.log(gen.next().value);

如您所见,我们可以使用循环重复运行 yield。的 yield 语句必须在代码的顶层运行。这意味着它们不能嵌套在另一个回调函数中。的 next 函数自动包含在生成器对象中,生成该对象可从生成器获取下一个值。

return 迭代器结束时调用方法。也就是说,当获得最后一个值或使用 thrown 方法。如果我们不希望它结束​​,我们可以将 yield 内的陈述 try...finally 子句类似于以下代码:

function* genFn() {
  try {
    yield;
  } finally {
    yield 'Keep running';
  }
}

当。。。的时候 throw 在运行生成器时会调用方法,除非生成器函数中捕获了错误,否则错误将停止生成器。要捕获,引发和捕获错误,我们可以编写类似以下代码的内容:

function* genFn() {
  try {
    console.log('Start');
    yield; // (A)
  } catch (error) {
    console.log(`Caught: ${error}`);
  }
}
const g = genFn();
g.next();
g.throw(new Error('Error'))

如您所见,如果我们运行上面的代码,则可以看到在运行第一行时记录了“开始”,因为我们只是从生成器对象获取第一个值,即 g.next() 线。然后 g.throw(new Error('Error')) 行运行会引发错误,该错误记录在 catch 条款。

使用生成器函数,我们还可以使用 yield* 关键词。以下示例无效:

function* genFn() {
  yield 'a'
}
function* genFnToCallgenFn() {
  while (true) {
    yield genFn();
  }
}
const g = genFnToCallgenFn()
console.log(g.next())
console.log(g.next())

如您所见,如果我们运行上面的代码, value 记录的属性是生成器函数,这不是我们想要的。这是因为 yield 关键字未直接从其他生成器检索值。这是 yield* 关键字很有用。如果我们更换 yield genFn();yield* genFn();,则生成器返回的值由 genFn 将被检索。在这种情况下,它将继续获取字符串“ a”。例如,如果我们改为运行以下代码:

function* genFn() {
  yield 'a'
}
function* genFnToCallgenFn() {
  while (true) {
    yield* genFn();
  }
}
const g = genFnToCallgenFn()
console.log(g.next())
console.log(g.next())

我们将看到 value 两个记录的对象中的属性具有 value 属性设置为“ a”。

使用生成器,我们可以编写一种迭代方法来轻松遍历树。例如,我们可以编写以下代码:

class Tree{
  constructor(value, left=null, center=null, right=null) {
    this.value = value;
    this.left = left;
    this.center = center;
    this.right = right;
  }

  *[Symbol.iterator]() {
    yield this.value;
    if (this.left) {
      yield* this.left;
    }
    if (this.center) {
      yield* this.center;
    }
    if (this.right) {
      yield* this.right;
    }
  }
}

在上面的代码中,我们拥有的唯一方法是生成器,该生成器返回树的当前节点的左,中和右节点。请注意,我们使用了 yield* 关键字代替 yield 因为JavaScript类是生成器函数,而我们的类是生成器函数,因为我们有用表示的特殊函数 Symbol.iterator 符号,表示该类将创建一个生成器。

符号是ES2015的新增功能。它是唯一且不变的标识符。创建后,将无法复制。每次创建新符号时,它都是唯一的。它主要用于对象中的唯一标识符。这是Symbol的唯一目的。

有一些静态属性和方法本身可以公开全局符号注册表。它就像一个内置对象,但没有构造函数,因此我们无法编写 new Symbol 构造一个Symbol对象 new 关键词。

Symbol.iterator 是一个特殊符号,表示该函数是迭代器。它内置在JavaScript标准库中。

如果我们具有以下定义的代码,则可以构建树数据结构:

const tree = new Tree('a',
  new Tree('b',
    new Tree('c'),
    new Tree('d'),
    new Tree('e')
  ),
  new Tree('f'),
  new Tree('g',
    new Tree('h'),
    new Tree('i'),
    new Tree('j')
  )
);

然后,当我们运行时:

for (const str of tree) {  
    console.log(str);  
}

我们以定义树的顺序获取树的所有值。定义递归数据结构比没有生成器函数要容易得多。

我们可以混合 yieldyield* 在一个生成器函数中。例如,我们可以这样写:

function* genFn() {
  yield 'a'
}
function* genFnToCallgenFn() {
  yield 'Start';
  while (true) {
    yield* genFn();
  }
}
const g = genFnToCallgenFn()
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())

如果我们运行上面的代码,我们将获得“开始”作为 value 返回的第一项的属性 g.next()。然后记录的其他所有项目都以“ a”作为 value 属性。

我们也可以使用 return 语句以返回要从迭代器返回的最后一个值。它的行为就像最后一个 yield 生成器函数中的语句。例如,我们可以这样写:

function* genFn() {
  yield 'a';
  return 'result';
}
const g = genFn()
console.log(g.next())
console.log(g.next())
console.log(g.next())
console.log(g.next())

如果我们查看控制台日志,可以看到我们记录的前2行在 value 属性,并在 value 前2个属性 console.log 线。然后剩下的一个 undefined 作为 value。首先 console.logdone 调成 false,而其余​​的 done 调成 true。这是因为 return 语句结束了生成器函数的执行。低于它的任何东西都无法像普通的一样 return 声明。

异步发电机

生成器也可以用于异步代码。要为异步代码生成一个生成器函数,我们可以创建一个带有特殊符号表示的方法的对象 Symbol.asyncIterator 功能。例如,我们可以编写以下代码来遍历一系列数字,将每次迭代间隔1秒:

const rangeGen = (from = 1, to = 5) => {
  return {
    from,
    to,
    [Symbol.asyncIterator]() {
      return {
        currentNum: this.from,
        lastNum: this.to,
        async next() {
          await new Promise(resolve => setTimeout(
            resolve, 1000));
          if (this.currentNum <= this.lastNum) {
            return {
              done: false,
              value: this.currentNum++
            };
          } else {
            return {
              done: true
            };
          }
        }
      };
    }
  };
}

(async () => {
  for await (let value of rangeGen()) {
    console.log(value);
  }
})()

请注意,promise解析为的值位于 return 陈述。的 next 函数应该总是返回一个promise。我们可以使用来迭代生成器生成的值 for await...of 循环,用于遍历异步代码。这非常有用,因为它可以像异步代码一样循环遍历异步代码,而这在我们拥有异步生成器函数和 asyncawait 句法。我们返回一个带有 donevalue 属性与同步发电机一样。

我们可以通过编写以下代码来缩短上面的代码:

async function* rangeGen(start = 1, end = 5) {
  for (let i = start; i <= end; i++) {
    await new Promise(resolve => setTimeout(resolve, 1000));
    yield i;
  }
}
(async () => {
  for await (let value of rangeGen(1, 10)) {
    console.log(value);
  }
})()

请注意,我们可以使用 yield 与运算符 asyncawait。承诺仍会在末尾返回 rangeGen,但这是一种更短的方法。它的功能与之前的代码完全相同,但是它更短,更容易阅读。

生成器函数对于创建可与 for...of 环。的 yield 语句将获得迭代器从您选择的任何源返回的下一个值。这意味着我们可以将任何东西变成可迭代的对象。另外,我们可以使用Symbol表示的方法定义一个类,从而使用它遍历树结构 Symbol.iterator,这将创建一个生成器函数,该函数将使用 yield* 关键字,它直接从生成器函数中获取一项。另外,我们有 return 语句以返回生成器函数中的最后一项。对于异步代码,我们有 AsyncIterators,我们可以使用定义 asyncawaityield 就像我们上面所做的那样以顺序解决承诺。


资讯来源:由0x资讯编译自DEV,原文:https://dev.to/aumayeung/easy-introduction-to-javascript-generators-5cai ,版权归作者所有,未经许可,不得转载
你可能还喜欢