ES6学习笔记之Generator函数

以前断断续续接触到了一些ES6的知识,异步编程方面听得比较多的就是Promise,直到最近比较系统地学习了ES6的新特性才发现Generator这个神奇的存在,它能够实现一些史无前例的事情,让我顿时对它充满了兴趣。javascript

为何须要Generator?

JavaScript异步编程是为解决JavaScript执行环境是“单线程”这个问题的。在JavaScript中,异步编程的使用很是频繁,也常常会出现须要逐步完成多个异步操做的状况。以前用回调函数实现异步编程若是碰到了这种问题就须要嵌套使用回调函数,异步操做越多,嵌套得就越深,这样很是不利于代码的维护,代码阅读起来也很困难。Generator函数是ES6提出的一种异步编程解决方案,它能够避免回调的嵌套,可是它的用处可不只仅如此哦,待我细细道来。java

举个小例子

function* gen1() {
    yield 1;
    yield 'hello';
    return true;
}
let g1 = gen1();
g1.next();  // Object {value: 1, done: false}
g1.next();  // Object {value: "hello", done: false}
g1.next();  // Object {value: true, done: true}
g1.next();  // Object {value: undefined, done: true}

上面的代码就定义了一个Generator函数,Generator函数的定义跟普通函数差很少,只是在function关键字后面加了一个星号。调用Generator函数后和普通函数不一样的是,该函数并不当即执行,也不返回函数执行结果,而是返回一个指向内部状态的generator对象,也能够看做是一个遍历器对象。而后必须调用该对象的next方法,让函数继续走下去,是指针移向下一个状态。每当碰到yield语句,内部指针就停下来,直到下一次调用next()才开始执行。
上面代码调用了四次next方法,遍历才结束。next方法会返回一个有两个属性的对象,value属性的值为当前yield语句的值,done属性的值表示遍历是否结束,即最后一次调用next方法时,再也碰不到yield或者return语句了。
星号写在哪
function关键字和函数名之间的星号写在哪均可以,只要在二者之间便可,可是通常都采起我上面代码的那种写法。es6

Generator函数本质

上面说了那么多,想必你们已经知道Generator函数是怎么用的了,那么Generator本质上究竟是个啥呢?Generator函数的理解有多种:编程

  1. Generator函数能够被理解成一个状态机,里面封装了多种状态,有兴趣的同窗能够去了解一下状态机,操做系统的书里都会讲到。异步

  2. Generator函数还能够被理解成一个遍历器对象生成器,它返回的遍历器对象能够依次遍历Generator函数内部的每个状态。这就是为何以前说Generator函数不只是为了解决回调函数嵌套问题。Generator函数是生成一个对象,可是调用的时候前面不能加new命令异步编程

yield语句

yield语句是Generator函数内部能够暂停执行程序的语句,yield语句后面的值能够是各类数据类型,字符串,整数,布尔值等等均可以。这里主要想说说Generator函数中yield语句和return语句的区别。函数

和return语句区别

从上面的例子能够看出,函数不只是碰到yield语句才会中止执行,碰到return语句也会中止执行。这很容易理解,无论怎样Generator函数也是一个函数,碰到return语句必然会中止执行,返回值。那么,二者的区别是什么呢?先来看个例子:学习

function* gen2() {
    return true;
    yield 1;
    yield 'hello';
}
let g2 = gen2();
g2.next();  // Object {value: true, done: true}
g2.next();  // Object {value: undefined, done: true}

从上面例子能够看出,当碰到return语句时,返回对象的done属性值就为true,遍历结束,无论后面是否还有yield或者return语句。这种区别本质上是由于yield语句具有位置记忆功能而return语句则没有该功能。this

再说一点

Generator函数,无论内部有没有yield语句,调用函数时都不会执行任何语句,只有当调用next(),内部语句才会执行,只要调用next(),就会返回一个对象。yield语句只是函数暂停执行的一个标记。操作系统

function* gen3() {
    console.log('执行了么?');
}
let g3 = gen3();  // 没有任何输出
g3.next();
// 执行了么?
// Object {value: undefined, done: true}

注意:yield函数不能在普通函数中使用,不然会报错。

next方法

除了yield语句,next方法也是Generator函数实现中很重要的特性。既然next()是一个函数,那么这个函数能够带参数么,固然能够。上面的例子比较简单,都只是一些单纯的yield语句,其实Generator函数和普通函数同样里面是能够进行各类复杂的计算和操做的,也能够有各类循环语句,不只next方法能够传参数,Generator函数也是能够传参数的,立立刻例子:

function* gen4(a) {
    let b = yield (a + 1);
    return b * 2;
}
let g4 = gen4(1);
g4.next();  //  Object {value: 2, done: false}
g4.next();  //  Object {value: NaN, done: true}
let g5 = gen4(1);
g5.next();  //  Object {value: 2, done: false}
g5.next(3);  //  Object {value: 6, done: true}

上面例子中,Generator函数须要接收一个参数a,表面上变量b是用yield语句赋值了,可是遗憾的是这个赋值好像并无成功,当第二次调用next方法(没有传参数)时,返回的对象value值竟然为NaN,而不是咱们想的 2 *(1+1)= 4。可是若是第二次调用next方法时,传入一个参数3,返回对象的value值就为6。这能够说明两点:

  1. yield语句没有返回值,或者老是返回undefined;

  2. next方法若是带上一个参数,这个参数就是做为上一个yield语句的返回值。

注意:由于next方法表示上一个yield语句的返回值,因此必须有上一个yield语句的存在,那么第一次调用next方法时就不能传参数。第一个next只是用来启动Generator函数内部的遍历器,传参也没有多大意义。

再说Generator函数与普通函数区别

能够用prototype么?

虽然Generator函数和普通函数区别很大,可是Generator函数的实例也能够继承Generator函数的prototype对象上的方法。

function* gen5() {}
gen5.prototype.say = function() {
    console.log('有generator?');
}
let g6 = gen5();
g6.say();  // 有generator?

从上面代码能够看出,Generator函数返回的g6,继承了gen5.prototype。

this咋用?

你们都知道普通函数都会有一个this对象,那么Generator的this对象怎么用呢?仍是例子更直观:

function* gen6() {
    this.a = 1;
}
let g7 = gen6();
g7.a;  //  undefined

上面代码中,Generator函数在this对象上添加了一个属性a,g7实例并不能取到这个属性。那么怎么让Generator函数返回一个能够正常使用this对象的实例呢?阮一峰老师提供了一种方法,首先,生成一个空对象,使用call方法绑定Generator函数内部的this。这样,构造函数调用之后,这个空对象就是Generator函数的实例对象了。参考代码在这:http://es6.ruanyifeng.com/#docs/generator

Generator函数与Iterator

Generator函数返回的是一个遍历器对象,那么它在遍历这方面确定有用武之地,下一次讨论Iterator时候再总结吧。

相关文章
相关标签/搜索