前面文章介绍了Iterator,本文继续介绍Generator。
Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数彻底不一样。
---本文摘选阮一峰《ECMAScript 6 标准入门》
Generator函数从语法上是一个状态机,封装了多个内部状态,执行Generator函数会返回一个遍历器对象,返回的遍历器对象,能够依次遍历Generator函数内部的每个状态。
编程
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
Generator函数返回的遍历器对象,只有调用 next 方法才会遍历下一个内部状态,因此提供了 yield 暂停执行函数。异步
遍历器对象的 next 方法运行逻辑以下:异步编程
yield语句与return的区别:函数
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
任意一个对象的 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 方法能够带一个参数,该参数会做为上一个 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函数返回的遍历器对象,都有一个 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 函数内部的
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。
若是在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 函数老是返回一个遍历器,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对象
那文章的最后仍是老规矩,欢迎你们点赞和纠错。
祝各位周一愉快!