【探秘ES6】系列专栏:生成器
ES6作为新一代JavaScript标准,即将与广大前端开发者见面。为了让大家对ES6的诸多新特性有更深入的了解,Mozilla Web开发者博客推出了《ES6 In Depth》系列文章。CSDN已获授权,将持续对该系列进行翻译,供大家学习借鉴。本文为该系列的第三篇。
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*开头;
- 在生成器函数中,yield是一个关键字,如同return。yield可以多次使用,作用是中断生成器,
而在需要的时候可以恢复生成器的执行。
所以生成器函数最大的特点是可以中断自己,但普通函数不可以。
生成器的作用
当使用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 timesfor (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中贯穿始终的新的数据和循环标准。
生成器和异步代码
请看一段代码:
}).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
相关阅读:
【探秘ES6】系列专栏:ES6简介
【探秘ES6】系列专栏:迭代器和for-of循环
欢迎加入CSDN前端交流群:218126086,进行前端技术交流。