【探秘ES6】系列专栏(三):生成器

ES6做为新一代JavaScript标准,即将与广大前端开发者见面。为了让你们对ES6的诸多新特性有更深刻的了解,Mozilla Web开发者博客推出了《ES6 In Depth》系列文章。CSDN已获受权,将持续对该系列进行翻译,组织成【探秘ES6】系列专栏供你们学习借鉴。本文为该系列的第三篇。前端

ES6生成器介绍es6

什么是生成器呢?编程

请先看看如下代码。数组

function* quips(name) {  
  yield "hello " + name + "!";  
  yield "i hope you are enjoying the blog posts";  
  if (name.startsWith("X")) {  
    yield "it's cool how your name starts with X, " + name;  
  }  
  yield "see you later!";  
}

这是一段有关汤姆猫(talking cat)的代码。它看上去像是一个函数,是吗?在ES6中,它的名字是生成器函数,其与普通函数有不少类似的地方。但有两点不一样:
异步

  • 生成器函数以function*开头;async

  • 在生成器函数中,yield是一个关键字,如同return。yield能够屡次使用,做用是中断生成器,ide

    而在须要的时候能够恢复生成器的执行异步编程

因此生成器函数最大的特色是能够中断本身,但普通函数不能够。函数

生成器的做用工具

当使用quips()生成器函数时会出现什么状况呢?

> var iter = quips("jorendorff");  
  [object Generator]  
> iter.next()  
  { value: "hello jorendorff!", done: false }  
> iter.next()  
  { value: "i hope you are enjoying the blog posts", done: false }  
> iter.next()  
  { value: "see you later!", done: false }  
> iter.next()  
  { value: undefined, done: true }


对于普通quips(),它会立刻执行直到出现返回或异常抛出等状况。在生成器函数中,调用方式是相似的:quips("jorendorff"),可是它不会立刻执行。取而代之的是,它会返回一个已暂停的生成器对象(如上述代码的iter)。你能够把生成器对象当作是一个被暂停的函数调用。要特别说明的是生成器对象在生成器函数开始时被冻结,即第一行代码执行以前。

每当调用生成器对象的.next()方法时,函数恢复运行直至遇到下一个yield表达式,其做用是用于迭代。所以iter.next()的目的是为了返回不一样的字符串。在最后的iter.next()中,使用done:true表示结束。到达函数末端意味着返回的结果是undefined,因此代码片断中使用value: undefined结尾。

从技术角度来看,每当生成器执行yield操做时,它的堆栈帧包括本地变量、参数、临时值等都会从堆中被移出。可是生成器对象会保留(拷贝)对该帧的引用,因此.next()能够从新激活它而后继续执行。这里特别要说明的是,生成器不是线程。当一个生成器执行时,它与其调用者都处于同一个线程,是按次序执行而不是并行运行。

可见生成器的做用是暂停自己的运行,而后恢复并继续执行,那么这究竟有何用处呢?

生成器就是迭代器

ES6迭代器不是内建的,可尝试经过使用[Symbol.iterator]()和.next()来进行建立。可是这种相似接口的作法不是最简便的方法。请看下面一个range迭代器例子,它的做用相似于C的for(;;)循环。

// This should "ding" three times  
for (var value of range(0, 3)) {  
  alert("Ding! at floor #" + value);  
}

具体的实现代码:

class RangeIterator {  
  constructor(start, stop) {  
    this.value = start;  
    this.stop = stop;  
  }  
  
  [Symbol.iterator]() { return this; }  
  
  next() {  
    var value = this.value;  
    if (value < this.stop) {  
      this.value++;  
      return {done: false, value: value};  
    } else {  
      return {done: true, value: undefined};  
    }  
  }  
}  
  
// Return a new iterator that counts up from 'start' to 'stop'.  
function range(start, stop) {  
  return new RangeIterator(start, stop);  
}

要查看运行状况请点击这里

可见迭代器的生成并非件简单的事情。那么若是采用生成器来实现,应该如何编写呢?

function* range(start, stop) {  
  for (var i = start; i < stop; i++)  
    yield i;  
}

要查看运行状况请点击这里

对比是很明显的,上述代码仅需4行代码就完成了相同的功能,由于生成器就是迭代器。全部生成器都内建了对.next()和[Symbol.iterator]()的支持,你只须要负责循环的实现就能够了。

除此之外,做为迭代器使用的生成器还能够实现哪些功能呢?

  • 使任何对象可迭代。方法是编写一个生成器函数,而后对每一个值进行迭代。而后使用对象的[Symbol.iterator]方法与生成器函数进行绑定。

  • 简化数组功能。若是要实现以数组形式返回函数结果,能够这样写:

// Divide the one-dimensional array 'icons'  
// into arrays of length 'rowLength'.  
function splitIntoRows(icons, rowLength) {  
  var rows = [];  
  for (var i = 0; i < icons.length; i += rowLength) {  
    rows.push(icons.slice(i, i + rowLength));  
  }  
  return rows;  
}

若是使用生成器编写,能够把代码简化为:

function* splitIntoRows(icons, rowLength) {  
  for (var i = 0; i < icons.length; i += rowLength) {  
    yield icons.slice(i, i + rowLength);  
  }  
}

后者与前者的区别是否是一次就计算全部结果并返回一个数组,而是先返回一个迭代器,而后按次序按需进行计算。

  • 返回特殊长度数组。数组是有长度限制的,可是透过生成器迭代特性,能够产生无限的序列。

  • 重构复合循环。当编写一个复杂的循环时,能够提出产生数据的部分,把它改写为一个独立的生成器函数。例如(var data of myNewGenerator(args))。

  • 进行迭代运算的配套工具。ES6并无提供有关筛选、映射、迭代数据集操做的扩展库。可是借助生成器,咱们能够围绕它来简单地建立相关工具。

例如,要在DOM节点上实现与Array.prototype.filter相似的功能,能够这样编写:

function* filter(test, iterable) {  
  for (var item of iterable) {  
    if (test(item))  
      yield item;  
  }  
}

可见,生成器真的妙趣横生。借助生成器能够方便地实现定制的迭代操做,而迭代是ES6中贯穿始终的新的数据和循环标准。

生成器和异步代码

下面是我以前写过的一段代码:

         };
        })
      });
    });
  });
});

可能你的代码中也会看到相似的片断。异步APIs一般须要回调函数,这意味着每当作一些事情时就写一个匿名函数。因此若是你须要作三件事情,编写三行代码可不行,而是须要三段一致的代码。

请看我写过的一段代码:

}).on('close', function () {  
  done(undefined, undefined);  
}).on('error', function (error) {  
  done(error);  
});


异步APIs提供的是错误处理而非异常处理,不一样APIs有不一样的处理方法。而大多数错误定义是默认的,因此进行异步编程时须要花必定时间去了解。生成器则提供了新的处理方式。

Q.async()是一个实验性的相似于同步代码的异步代码生成方法,请看代码:

// Synchronous code to make some noise.  
function makeNoise() {  
  shake();  
  rattle();  
  roll();  
}  
  
// Asynchronous code to make some noise.  
// Returns a Promise object that becomes resolved  
// when we're done making noise.  
function makeNoise_async() {  
  return Q.async(function* () {  
    yield shake_async();  
    yield rattle_async();  
    yield roll_async();  
  });  
}


二者的主要区别是异步代码必须使用yield关键字来执行异步函数。所以生成器为新的异步编程模型带来了新的思路,更符合人的思惟习惯。

写在最后

限于篇幅,生成器还有两个方法留待后续讲解,.throw()和.return()。多看官方文档多动手练习,你会发现ES6更多精彩。(译者:伍昆 责编:陈秋歌)

原文连接:ES6 In Depth: Generators

本译文遵循Creative Commons Attribution Share-Alike License v3.0 

相关文章
相关标签/搜索