什么是天生器?
我们先从下面的这里例子最先。
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!";
}
这段代码是一个对话猫,这多是当前收集上最重要的一类运用。
这个在肯定程度上看起来像一个函数,对不?这就被称为天生器函数,同时它与函数之间也有许多相似之处。然则你一会儿就能够发明两个差别之处:
平常的函数运用function作为最先。天生器函数以function*最先。 在一个天生器函数中,yield是一个关键字,语法和return很相似。 区分在于,一个函数(以至是天生器函数),只能返回一次,然则一个天生器函数能够yield许屡次。 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 }
你能够非常习惯于平常的函数以及他们的表现。当你挪用他们的时刻,他们马上最先运转,当碰到 return或许throw的时刻,他们住手运转。任何一个JS程序员都非常习惯于上述的历程。
挪用一个天生器看起来是一样的:quips(”jorendorff”)。 然则当你挪用一个天生器,他还不最先运转。反而,它返回一个停息的天生器对象(在上述的例子中被称为iter)。你能够以为这个天生器对象是一个函数挪用,临时住手。迥殊的是,其在天生器函数一最先就住手了,在运转代码的第一行之前。
每次你挪用天生器对象的.next()要领,函数将其自身冻结并运转直到其抵达下一个yield表达式。
这就是上面代码中我们为何要挪用iter.next(),挪用后我们取得一个差别的字符串值。这些值都是由quips()里的yield表达式发生的。
在末了一个iter.next( )挪用中,我们末了完毕了天生器函数,所以效果的.done范畴的值为true。 一个函数的完毕就像是返回undefined,而且这也是为何效果的.value范畴是不肯定的。
如今多是一个好机会来返回到上面的对话猫的例子那页面上,同时真正地能够玩转代码。尝试着在一个轮回中到场一个yield。这会发作什么呢?
在手艺层面上,每一次一个天生器举行yield操纵,其客栈桢,包括局部变量,参数,临时价,以及在天生器中的实行的当前位置,被从栈中删除。但是,天生器对象保有这个客栈帧的援用(或许是副本)。因而,接下来的挪用.next( )能够从新激活它并继承实行。
值得指出的是,天生器都没有线程。在能运用线程的言语中,多份代码能够在同一时间运转,这平常致使了合作前提,非肯定性和非常非常好的机能。天生器和这完整差别。当天生器运转时,它与挪用者运转在同一个线程中。实行的递次是一连且肯定的,并永久不会并发。差别于体系线程,天生器只会在代码顶用yield标记的处所才会吊挂。
好了。我们晓得天生器是什么了。我们已看到了天生器器运转,停息,然后恢复实行。如今的大题目是,如许新鲜的才能怎样多是有效的呢?
天生器是迭代器,我们已看到了ES6的迭代器不只是一个简朴的内置类。他们是言语的扩大点。你能够经由过程完成两种要领来建立你自身的迭代器,这两种要领是:Symbol.iterator和.next()。
然则,完成接口老是最少照样有一点事变量的。让我们来看看一个迭代器实如今实践中看起来是什么样的。由于是一个例子,让我们运用一个简朴的局限(range)迭代器,只简朴的从一个数字数到另一个数字,就像一个老式的C言语的for(;;)轮回。
// 这个应当三次发出“叮”的声响
for (var value of range(0, 3)) {
alert("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);
}
在现实运转中看这段代码(http://codepen.io/anon/pen/NqGgOQ)。
这就是像是在Java或Swift言语里完成一个迭代器。它不是那末蹩脚。但它也并非那末简朴。在这个代码中有没有任何毛病?这就不好说了。它看起来完整不像我们想在这里模拟的本来的for(;;)轮回:迭代器协定迫使我们扬弃了轮回。
在这一点上,你能够会对迭代器不太热忱。他们能够对运用来讲很棒,但他们好像很难完成。
你能够不会发起我们只是为了简朴的建立迭代器,而在JS言语中引进一个庞杂的新的掌握流构造。然则,由于我们确切有天生器,我们能在这里运用它们吗?让我们试一试:
function* range(start, stop) {
for (var i = start; i < stop; i++)
yield i;
}
在这里看代码的详细运转(http://codepen.io/anon/pen/mJewga)。
上述的四行的天生器是对先前range()的二十三行的完成的一个直接替换,包括了悉数RangeIterator类。这多是由于天生器是迭代器。一切的天生器都有一个内置的对.next()已Symbol.iterator要领的完成。
不运用天生器来完成迭代器就像是被强制用被动语气写一封很长的邮件。本来想简朴地想表达你的意义,能够到末了你说的会变得相称令人费解。RangeIterator是很长且奇异的,由于它必需不运用轮回语法来形貌一个轮回的功用。天生器是答案。
我们还能怎样运用天生器作为迭代器的才能?
使对象可迭代。只要写一个迭代器函数来一向挪用this,在其涌现的处所天生每一个值。然后以该对象的[Symbol.iterator]要领来装置该天生器函数。
简化数组构建函数。完成一个函数,每当其被挪用就会返回一个数组,以下面的这个例子:
// 将一维数组'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轮回(myNewGenerator(args)的var数据)。
运用迭代的东西。ES6不供应扩大的库来举行过滤,映照以及平常能够接见恣意可迭代的数据集。但天生器是巨大的,你只须要用几行代码就能够够构建你须要的东西。举个例子说,假定你须要一个东西等同于Array.prototype.filter,其是在DOM NodeLists上事变的,不只是一个数组。这用代码来完成就是小意义:
function* filter(test, iterable) {
for (var item of iterable) {
if (test(item))
yield item;
}
}
这个就是为何天生器云云有效吗?固然。他们是要完成自定义的迭代器的简朴的要领。同时,迭代器在悉数ES6中是用于数据和轮回的新标准。
然则,这还不是天生器能做的事变的悉数。这以至有能够不是他们做的最重要的事变。
天生器和异步代码
这里是一个 JS 代码,我写了一个 while 的 back 部份。
};
})
});
});
});
});
能够这看起来和你代码的一部份比较相像。异步API平常情况下须要一个回调,这就意味着你做一些事变时要写一个分外的匿名函数。所以假如你有一部份代码做三件事变,而不是三行代码,你是在看三个缩进条理的代码。
这里有一些我已写好的 JS 代码:
}).on('close', function () {
done(undefined, undefined);
}).on('error', function (error) {
done(error);
});
异步API有毛病处理的商定,但不是运用非常。差别的API有差别的商定。在他们中的大多数中,毛病在默许情况下被默默地删除。其中有一些,即使是平常的圆满完成,在默许情况下都邑被删除。
直到如今,这些题目都只是简朴的转画为我们举行异步编程的价值了。我们已最先接收异步代码了,他们只是看起来不是像同步代码那样优美和简朴。
天生器供应了新的愿望,能够不必如许做的。
Q.async()是一个试验性的尝试。其运用迭代器来天生相似于同步代码的异步代码。举个例子:
//
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关键字。
在Q.async版本中增加一点小东西,如if语句或try/catch块,与在平常同步版本中增加是完整相同的。比拟于编写异步代码的其他体式格局,有种不是在进修一个全新的言语的觉得。
所以天生器指出了一个更适合人类大脑的新的异步编程模子。这项事变正在举行中。除其他事项外,更好的语法能够有所协助。
异步函数的提出,建立在两边的许诺和天生器的基础上,并从在C#相似的功用中采用灵感,这些都是ES7要做的事变。
我什么时刻能够用这猖獗的东西?
在服务器端,我们如今能够在io.js中运用 ES6 天生器。假如你启用–harmony选项,我们在Node中可也已运用ES6天生器。
在浏览器中,当今只要火狐27版本以上以及谷歌浏览器39版本以上的支撑ES6天生器。为了在当今的收集上面运用天生器,你将须要运用Babel或许Traceur来将你的ES6的代码翻译为收集友爱的ES5代码。
一些重要的事宜值得相识
天生器是由布伦丹·艾希初次在JS上完成的。布伦丹·艾希的设想是牢牢追随由Icon启示的Python天生器。他们早在2006年就运用在火狐2.0版本上了。然则标准化的途径是崎岖不平的,而且语法和行动在这个历程当中转变了许多。ES6天生器是由编程黑客温格安迪在火狐浏览器和谷歌浏览器中完成的。这项事变是由Bloomberg资助的。
yield
关于天生器另有更多的说法。我们没有包括.throw()和.return()要领,可选的参数.next(),或yield*表达式语法。但我以为这个帖子已很长了,且如今已充足虚无缥缈了。像天生器自身,我们应当停下来歇息一下。