混子前端所知道关于ES6的Generator
栏目: JavaScript · 发布时间: 5年前
内容简介:前面文章介绍了Iterator,本文继续介绍Generator。Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。---本文摘选阮一峰《ECMAScript 6 标准入门》
前面文章介绍了Iterator,本文继续介绍Generator。
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同。
---本文摘选阮一峰《ECMAScript 6 标准入门》
基本概念
Generator函数从语法上是一个状态机,封装了多个内部状态,执行Generator函数会返回一个遍历器对象,返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。
Generator函数从形式上是一个普通函数,但是有两个特征:
- function 关键字与函数名之间有一个星号
- 函数体内部使用 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 方法运行逻辑如下:
- 遇到 yield 语句暂停执行后面的操作,并将在 yield 后面那个表达式的值,作为返回对象的 value 属性值
- 下次调用 next 方法时,再继续往下执行,直到遇到下一个 yield 语句
- 如果没有遇到新的 yield 语句,就一直运行函数结束 到 return 语句为止,将 return 的值作为返回的对象 value 的属性值
- 如果该函数没有 return 语句,则返回的对象的 value 属性值为 undefined
yield语句与return的区别:
- return 语句不具备位置记忆功能,即函数遇到 yield 函数暂停执行,下一次在从该位置继续执行。
- 一个函数里只能执行一次 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 } 复制代码
说明: 遍历器对象 g 调用 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 函数 当作普通的构造函数,并不会生效,同时 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对象
那文章的最后还是老规矩,欢迎大家点赞和纠错。
祝各位周一愉快!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 混子前端带你入门webpack
- 混子前端所知道关于ES6的Promise
- 混子前端所知道关于ES6的Iterator
- 混子前端在开发公众号嵌套webview遇到的常见问题
- 前端科普系列(三):CommonJS 不是前端却革命了前端
- 前端科普系列(三):CommonJS 不是前端却革命了前端
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。