混子前端所知道关于ES6的Generator

前面文章介绍了Iterator,本文继续介绍Generator。
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数彻底不一样。
---本文摘选阮一峰《ECMAScript 6 标准入门》

基本概念

Generator函数从语法上是一个状态机,封装了多个内部状态,执行Generator函数会返回一个遍历器对象,返回的遍历器对象,能够依次遍历Generator函数内部的每个状态。
编程

Generator函数从形式上是一个普通函数,可是有两个特征:
  1. function 关键字与函数名之间有一个星号
  2. 函数体内部使用 yield 语句,定义不一样的内部状态

JS code:

function* helloWorldGenerator() {
    yield 'hello';
    yield 'world';
    return 'ending';
}

var hw = helloWorldGenerator();

hw.next()    // { value: 'hello', done: false }
hw.next()    // { value: 'world', done: false }
hw.next()    // { value: 'ending', done: true }
hw.next()    // { value: undefined, done: true }
复制代码

说明:Generator函数 helloWorldGenerator有三个状态:hello,world 和 return 语句,这里调用 Generator 函数后并不执行,而是返回一个指向内部状态的指针对象,开发人员必须手动调用遍历器对象的next方法,使指针移动到下一个状态,即 Generator 函数是分段执行的,yield语句是暂停执行的标记,而 next 方法能够恢复执行。
bash

next 方法返回一个对象,对象value是当前 yield 语句的值,done表示遍历的状态 ( false: 没结束 ) app


yield 语句

Generator函数返回的遍历器对象,只有调用 next 方法才会遍历下一个内部状态,因此提供了 yield 暂停执行函数。异步

遍历器对象的 next 方法运行逻辑以下:异步编程

  1. 遇到 yield 语句暂停执行后面的操做,并将在 yield 后面那个表达式的值,做为返回对象的 value 属性值
  2. 下次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 语句
  3. 若是没有遇到新的 yield 语句,就一直运行函数结束 到 return 语句为止,将 return 的值做为返回的对象 value 的属性值
  4. 若是该函数没有 return 语句,则返回的对象的 value 属性值为 undefined 

yield语句与return的区别:函数

  1. return 语句不具有位置记忆功能,即函数遇到 yield 函数暂停执行,下一次在从该位置继续执行。
  2. 一个函数里只能执行一次 return 语句,但能够执行屡次 yield 语句

Generator函数能够不用 yield 语句,这时就变成了一个单纯的暂缓执行函数,来看代码:ui

JS code:

function* f() {
    console.log('执行了!')
}

var generator = f();    

setTimeout(function () {
    generator.next()
}, 2000);复制代码

注意:以上代码直接调用函数f(); 不会执行console语句,无论有没有 yield 语句都要执行 next 方法才能调用 Generator 函数,另外 yield 函数也不能存在普通函数中,不然会直接抛出错误,还须要注意的是在 Generator 函数的 forEach / map 等循环语句中,也不能够调用 yield 方法,不然会产生语法错误,另外,yield 语句若是在表达式中,必须用括号扩起来。this

与Iterator接口的关系

任意一个对象的 Symbol.iterator 方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。但因为 Generator 函数就是遍历器生成函数,所以能够把 Generator 赋值给对象的 Symbol.iterator 属性,从而使得该对象具备 Iterator 接口,来看代码:spa

JS code:

var myIterable = {};

myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};

[...myIterable]    // [1,2,3]
复制代码

说明:上面代码 Generator函数赋值给 Symbol.iterator 属性,所以 myIterable 对象具备了Iterator接口,能够被 ... 运算符遍历了。prototype


next方法的参数

next 方法能够带一个参数,该参数会做为上一个 yield 语句的返回值,来看代码:

JS code:

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

var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}

var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
复制代码

说明:

第一个 a.next() 没有任何争议;第二个 a.next() 为何抛出NaN,由于next 没有携带参数默认 undefined,致使 undefined / 3 后变成NaN;同理第三个 a.next() 中return(x+y+z),内部运做实际上是 5 + NaN + undefined,因此 value 也是 NaN;

下面来看一下第二次 b.next() value 输出8,是传递了12做为上一次yeild的值,y就变成了2*12=24,那24/3=8,因此value 为 8;来看一下第三次b.next(),此时 x 为 5 毫无争议,z是传递进去的13,因为上一次next传入的值为12,y=12*2=24,因此return(5+24+13)输出42;

注意:next 方法的参数表示上一个 yield 语句的返回值,因此第一次使用 next 不能带有参数


Generator.prototype.throw()

Generator函数返回的遍历器对象,都有一个 throw 方法,能够在函数体外抛出错误,而后在Generator函数体内捕获,来看代码:

JS code:

var g = function* () {
    try {
        yield;
    } catch (e) {
        console.log('内部捕获', e);
    }
};

var i = g();
i.next();

try {
    i.throw('a');
    i.throw('b');
} catch (e) {
    console.log('外部捕获', e);
}
// 内部捕获 a
// 外部捕获 b

复制代码

说明:上面代码遍历器 i 连续抛出两个错误,第一个错误被 Generator 函数题内 catch 捕获,i 的第二个错误因为 Generator 函数内部的

 
catch  语句已经执行过了,不会再捕捉到这个错误了, 因此这个错误被抛出 Generator 函数体,被函数体外的  catch  语句捕获。


Generator.prototype.return()

Generator函数返回的遍历器对象 return 方法,能够返回给定的值,终结遍历Generator函数。

JS code:

function* gen() {
    yield 1;
    yield 2;
    yield 3;
}

var g = gen();

g.next()         // { value: 1, done: false }
g.return('foo')  // { value: "foo", done: true }
g.next()         // { value: undefined, done: true }
复制代码

说明:遍历器对象 调用 return 方法后,返回值 value 属性就是 return 方法的参数 foo,同时,Generator函数的遍历就终止了,done 值置为true。


yield*语句

若是在Generater函数内部,调用另外一个Generator函数,默认状况下是没有效果的,来看代码:

JS code:

function* foo() {
    yield 'a';
    yield 'b';
}

function* bar() {
    yield 'x';    
    foo();
    yield 'y';
}

function* gen() {
    yield 'x';    
    yield* foo();
    yield 'y'
}

for (let v of bar()){
    console.log(v);
}// 'x'
// 'y'
for (let v of gen()){
    console.log(v);
}
// 'x'
// 'a'
// 'b'
// 'y'复制代码

说明:上面代码在 bar 里面调用 foo,是不会有效果的,要用到 yield* 语句,用来在一个Generator 函数里面执行另外一个 Generator 函数。


Generator函数的this

Generator 函数老是返回一个遍历器,ES6规定这个遍历器是 Generator 函数的实例,也继承了Generator 函数的 prototype 对象上的方法。
但若是把 Generator 函数看成普通的构造函数,并不会生效,同时 Generator 函数也不能跟new 命令一块儿用,来看代码:

JS code:

function* gen() {
    this.a = 1;
    yield this.b = 2;
    yield this.c = 3;
}

function F() {
    return gen.call(gen.prototype);
}

var f = new F();

f.next();  // Object {value: 2, done: false}
f.next();  // Object {value: 3, done: false}
f.next();  // Object {value: undefined, done: true}

f.a // 1
f.b // 2
f.c // 3
复制代码

说明:能够更换执行者 F 内部的 this 对象绑定对象,而后调用它,返回一个Iterator对象


那文章的最后仍是老规矩,欢迎你们点赞和纠错。

祝各位周一愉快!

相关文章
相关标签/搜索