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

栏目: JavaScript · 发布时间: 5年前

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

前面文章介绍了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语 句是暂停执行的标记,而 n ext  方法可以恢复执行。

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

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 语句,这时就变成了一个单纯的暂缓执行函数,来看代码:

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 语句如果在表达式中,必须用括号扩起来。

与Iterator接口的关系

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

JS code:

var myIterable = {};

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

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

说明: 上面代码  Generator函数赋值给  Symbol.iterator  属性,因此  myIterable  对象具有了Iterator接口,可以被  ...  运算符遍历了。

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对象

那文章的最后还是老规矩,欢迎大家点赞和纠错。

祝各位周一愉快!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

硅谷创投课

硅谷创投课

[美]加里·维纳查克 / 林怡 / 北京联合出版社 / 2017-6 / 52

☆通用电气前CEO杰克·韦尔奇力荐,影响500强企业CMO的美国互联网意见领袖全新力作! ☆《纽约时报》榜单全新畅销书,把握来自硅谷的互联网风口浪潮! ☆70后创投鬼才,影响美国00后一代商业观的网络红人、科技公司天使投资人面对面解答你创投、管理、运营中的 一切困惑! ☆来自无数实战的真实商业意见!年轻人为什么买你的账?投资人凭什么会把钱交给你?企业家更应该做的事到底是什么?告诉......一起来看看 《硅谷创投课》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器