ES6生成器基础

    ES6引进的最使人兴奋的特性就是一种新的函数生成方式,称为生成器(generator)。名称有点奇怪,可是第一眼看上去行为更加奇怪。文章主要介绍生成器如何工做,而后让你明白为何他们对于将来的JS会有很大的影响。es6

完成运行编程

    首先看看生成器和普通函数有什么不一样。不管你是否已经意识到,关于你的函数,老是能够很基本的假设一些东西:一但函数开始运行,它老是在其余JS代码能够运行前运行完毕。例子:设计模式

setTimeout(function(){
    console.log("Hello World");
},1);

function foo() {
    // NOTE: don't ever do crazy long-running loops like this
    for (var i=0; i<=1E10; i++) {
        console.log(i);
    }
}

foo();
// 0..1E10
// "Hello World"

    在这里,完成for循环会花至关长的时间,比1毫秒长不少,可是咱们的计时器回调函数console.log("Hello World")并不能在foo()函数运行期间打断他,因此它卡住了,排在最后面(在循环后面),耐心地等待轮到他。数组

    可是若是foo()能够被打断是什么样的?难道不会对程序形成严重破坏吗?多线程

    这就是噩梦般的多线程编程挑战,可是很幸运,在JS领土中,不用担忧这种状况,由于JS老是单线程的(在任何给定的时间内,只有一条命令\函数执行)。并发

    注:Web开发人员对于一部分JS程序,能够做为一个独立的线程来运行,彻底与主JS线程并行。这样不会引发多线程并行到程序中的理由,是两个线程只能经过普通异步事件和彼此交互,而异步事件遵循的是一次只执行一条的原则。异步

运行...中止...运行函数

    经过ES6生成器,有了一种不一样的函数,这种函数能够停在中间,一次或者屡次,事后又恢复,容许其余代码在这个中止期运行。oop

    若是阅读过关于并发或者多线程的任何东西,会看到“合做”期,即一个进程(一个函数)本身选择何时容许打断,以便它能够和其余代码“合做”。这一律念与"先发"造成对比,代表进程/函数可能违背其意愿被打断。this

    ES6生成器函数在并发中是“合做”的。在生成器函数体内,可使用新的yield关键字从内部暂停本身。没有什么能够从外部暂停一个生成器,函数在内部遇到yield时能够暂停本身。

    可是,生成器函数包含yield暂停本身的时候,它不能恢复本身。必须使用外部控制来重启生成器。后续会介绍这个怎么发生的。

    因此,一个生成器函数能够中止,被从新启动,能够自定义次数。事实上,可使用一个无限循环(相似while (true) { .. })来指定一个生成器函数。在日常的JS程序里面这多是疯狂的或者是一个错误,对于生成器函数来讲是很理智的,有时就是你想要作到的!

    更重要的,这个中止和开始并非在生成器函数执行中的一个控制,容许2通道消息传入和传出生成器。在日常的函数中,在开始能够获取到参数,在结尾处得到一个return值。利用生成器函数,使用每一个yield向外输出消息,使用每一个start回送消息。

语法!

    让咱们看看这个新的、使人兴奋的生成器函数的语法。

    首先,新的声明语法:

function *foo() {
    // ..
}

    注意到这里的*了么?这就是全新的有点奇怪的格式。在其余的一些语言里面,看上去很像糟糕的函数返回指针。可是不要疑惑!这只是一个标记生成器函数的方式。
    你也许在其余文章里面看到过function* foo(){ } 来替代 function *foo(){ } (*的位置不一样),这两种状况都是合法的,可是最近以为function *foo() { }更明确一些,也就是这里用到的方式。

    如今咱们讨论一下生成器函数的内容。大多数状况下生成器函数只是普通的JS函数。在生成器函数内部基本没什么新的语法可学的。

    咱们主要必须玩会的新玩具,在上面提到过,就是yield关键字。yield__被称为"yield表达式"(不是声明)是由于当启动生成器的时候,咱们会传递一个值进去,传递进去的值会是yield表达式的计算结果。例如:

function *foo() {
    var x = 1 + (yield "foo");
    console.log(x);
}

    yield "foo"表达式会在暂停生成器函数的时候将foo值传出来,当生成器从新启动的时候,传递进来的值会是那个表达式的结果,会加1指给x.
    看到2通道的交互了么?你将"foo"传出,暂停了本身,在事后的某时间点(多是当即,也多是距离如今很长的一段时间),生成器函数会从新启动会传递一个回送值。

    在任何表达式的位置,在表达式或声明中能够单独使用yield。yield有一个假定的值undefined:

// note: `foo(..)` here is NOT a generator!!
function foo(x) {
    console.log("x: " + x);
}

function *bar() {
    yield; // just pause
    foo( yield ); // pause waiting for a parameter to pass into `foo(..)`
}

生成器迭代器

    迭代器是一种特殊的用法,事实上是一种设计模式,当使用next()进入一系列预选的值中的时候,想象下在一个有5个值的数组上使用迭代器的时候,第一个next()调用会返回1,第二个next()调用会返回2,以此类推。等到全部值被返回,next()会返回null或false或者其余标志位来表示你已经迭代完了全部的值。

    从外部控制生成器函数的方法是使用生成器迭代器构建和交互。听起来比实际复杂些。例子:

function *foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
}

    进入*foo()生成器的值中,须要构建一个迭代器:

var it = foo();

    因此使用日常的方法调用生成器函数不会执行它的任何一部分。

    这看起来有点奇怪。你也许想要知道,为何不是var it = new foo()。这语法后的缘由很复杂已经超出了这里的讨论范围。

    因此,如今开始迭代咱们的生成器函数,咱们只需:

var message = it.next();

    这会从yield 1声明返回1,但那不是咱们仅仅获得的。

console.log(message); // { value:1, done:false }

    实际上咱们从每一个next()调用获得了一个对象,它有一个yield传出值的value属性,done是一个表示生成器函数是否运行完成的布尔变量。

    继续看迭代器:

console.log( it.next() ); // { value:2, done:false }
console.log( it.next() ); // { value:3, done:false }
console.log( it.next() ); // { value:4, done:false }
console.log( it.next() ); // { value:5, done:false }

    有趣的是,done在咱们获得5这个值的时候仍然是false。这是由于,技术上,生成器函数没有完成。咱们不得不调用最后一个next()调用,若是咱们传入一个值,它必须做为yield5表达式的结果。只有那时生成器函数完成了。
    因此,如今:

console.log( it.next() ); // { value:undefined, done:true }

    生成器函数的最后的结果是咱们调用完成了,可是没有结果给出来。
    你也许想知道,在生成器函数内能够用return么,若是用了,那个值会在value属性里面输出么?

    是的...

function *foo() {
    yield 1;
    return 2;
}

var it = foo();

console.log( it.next() ); // { value:1, done:false }
console.log( it.next() ); // { value:2, done:true }

    不是...

    在生成器内依赖return不是一个好的作法,由于使用for-of循环迭代生成器的时候,最后return的值会被忽略。

    为下降风险,咱们迭代生成器的时候看看传递进去和出来信息:

function *foo(x) {
    var y = 2 * (yield (x + 1));
    var z = yield (y / 3);
    return (x + y + z);
}

var it = foo( 5 );

// note: not sending anything into `next()` here
console.log( it.next() );       // { value:6, done:false }
console.log( it.next( 12 ) );   // { value:8, done:false }
console.log( it.next( 13 ) );   // { value:42, done:true }

    能够看到,仍然能够经过内部的foo(5)迭代器实例化调用向内传递参数,就像普通函数同样,使x为5.
    第一个next()调用,咱们没有传递任何参数。为何?由于没有yield表达式来接收传递的参数。

 

英文原文:http://davidwalsh.name/es6-generators

相关文章
相关标签/搜索