本文是ES6学习的第二部分,主要学习的是生成器(GENERATORS)
GENERATORS与以前的JS特性截然不同,但是却使语言内部的常态行为变得更加强大
ES6生成器简介
首先用一个例子介绍生成器是什么?
function* quips(name) {
yield "你好 " + name + "!";
yield "希望你能喜欢这篇介绍 ES6 的译文";
if (name.startsWith("X")) {
yield "你的名字 " + name + " 首字母是 X,这很酷! ";
}
yield "我们下次再见! ";
}
上述代码看起来像一个函数,但是它是一个生成器函数,他们之间不同之处:
- 普通函数使用 function 声明,而生成器函数使用 function*声明。
- 在生成器函数内部,有一种类似 return 的语法:关键字 yield。二者的区别是,普
通函数只可以 return 一次,而生成器函数可以 yield 多次(当然也可以只 yield 一次)。
在生成器的执行过程中,遇到 yield 表达式立即暂停,后续可恢复执行状态。
这就是普通函数和生成器函数之间最大的区别,普通函数不能自暂停,生成器函数
可以
生成器做了什么?
> var iter = quips("jorendorff");
[object Generator]
> iter.next()
{ value: "你好 jorendorff!", done: false }
> iter.next()
{ value: "希望你能喜欢这篇介绍 ES6 的译文", done: false }
> iter.next()
{ value: "我们下次再见! ", done: false }
> iter.next()
{ value: undefined, done: true }
生成器的调用“quips("jorendorff")“,但是它并未立即执行,而是返回了一个已暂停的生成器对象(iter)
它在调用一次后被冻结了。每一次调用.next()方法时,函数调用将其自身解冻并一直运行到下一个yield表达式,每次调.next()直到生成器末尾,返回值结果中done = true,value=undefined。
如果用专业术语描述,每当生成器执行 yields 语句,生成器的堆栈结构(本地变量、参数、临时值、生成器内部当前的执行位置)被移出堆栈。然而,生成器对象保留了对这个堆栈结构的引用(备份),所以稍后调用.next()可以重新激活堆栈结构并且继续执行。
生成器不是线程,当生成器运行时,它和调用者处于同一线程中,拥有确定的连续执行顺序,永不并发。与系统线程不同的是,生成器只有在其函数体内标记为 yield 的点才会暂停。
生成器是迭代器
首先实现一个迭代器,创建一个简单的迭代器。可以实现两个数字之间的所以数相加,首先是传统C的for(;;)循环。
//应该弹出3次
for(var value of range(0,3)){ console.log("Ding! at floor #"+value);}
使用ES6的语法解决
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};
}
}
}
function range(start,stop){
return new RangeIterator(start,stop);
}
function* range(start, stop) {
for (var i = start; i < stop; i++)
yield i;
}
由上面的例子可以看出生成器是迭代器,所有生成器都内建有.next()和[Symbol,iterator]()方法的实现。
例子2:简化数组构建函数
// 拆分一维数组 icons
// 根据长度 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);
}
}
使用生成器是使用生成器返回一个迭代器,每次根据需要逐一地计算,而传统写法是计算所有结果并返回一个数组类型的结果。
当面对一个复杂的循环时,可以拆分出生成数据的代码,将其转换为独立的生成器函数,然后使用for(var data of myNewGenerator(args))遍历所要的数据 。
举个例子,假设你需要一个等效于 Array.prototype.filter 并且支持 DOM NodeLists
的方法,可以这样写:
function* filter(test, iterable) {
for (var item of iterable) {
if (test(item))
yield item;
}
}
生成器与异步代码
异步API通常需要一个回调函数,意味着需要为每次执行编写额外的异步函数,所以如果有有一段代码需要完
成三个任务,你将看到类似的三层级缩进的代码,而非简单的三行代码。
}).on('close', function () {
done(undefined, undefined);
}).on('error', function (error) {
done(error);
});
到目前为止,异步代码不如同步代码美观又简洁。
实验性的 Q.async()尝试结合 promises 使用生成器产生异步代码的等效同步代码。举个例子:
// 制造一些噪音的同步代码。
function makeNoise() {
shake();
rattle();
roll();
}
// 制造一些噪音的异步代码。
// 返回一个 Promise 对象
// 当我们制造完噪音的时候会变为 resolved
function makeNoise_async() {
return Q.async(function* () {
yield shake_async();
yield rattle_async();
yield roll_async();
});
}
二者主要的区别是,异步版本必须在每次调用异步函数的地方添加 yield 关键字。在 Q.async 版本中添加一个类似 if 语句的判断或 try/catch 块,如同向同步版本中添加类似功能一样简单。
如何应用这些疯狂的新特性?
在服务器端,现在你可以在 io.js 中使用 ES6(在 Node 中你需要使用--harmony 这
个命令行选项)。
在浏览器端,到目前为止只有 Firefox 27+和 Chrome 39+支持了 ES6 生成器。如果要在 web 端使用生成器,你需要使用 Babel 或 Traceur 来将你的 ES6 代码转译为 Web友好的 ES5
Yield
未完待续
参考资料《ES6-In-Depth》