摘自Generator 函数
Generator
函数是协程在ES6的实现,最大特点就是可以交出函数的执行权(即暂停执行)。
整个Generator函数就是一个封装的异步任务,或者说是异步任务的容器。异步操作需要暂停的地方,都用yield语句注明。
Generator
函数有多种理解角度。从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hello = helloWorldGenerator();
hello.next();// {value: "hello", done: false}
hello.next();// {value: "world", done: false}
hello.next();// {value: "ending", done: true}
hello.next();// {value: undefined, done: true}
调用Generator函数,返回一个遍历器对象,代表Generator
函数的内部指针。以后,每次调用遍历器对象的next
方法,就会返回一个有着value
和done
两个属性的对象。value
属性表示当前的内部状态的值,是yield
语句后面那个表达式的值;done
属性是一个布尔值,表示是否遍历结束。
next
方法的参数
yield
句本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
语句的返回值。
function* gen(){
var x = yield "a";
var y = yield "b";
return x + y;
}
var g = gen();
g.next(); // {value: "a", done: false}
g.next("hello"); // {value: "b", done: false}
g.next("world"); // {value: "helloworld", done: true}
第一个next
返回yield "a";
的value值a
,第二个next
返回yield "b";
的value值”b”。
但是next
接收参数hello
作为上一个yield "a"
语句的返回值, x
的值为hello
.
第三个next
接收参数world
作为上一个yield "b"
语句的返回值,y
的值为world
. return
返回的值就是helloworld
var g = gen();
g.next();//{value: "a", done: false}
g.next();//{value: "b", done: false}
//`next`方法没有传入参数时,`yield`语句返回`undefined`. undefined + undefined => NaN
g.next();//{value: "NaN", done: true}
第一个next方法用来执行完第一个yield
语句,所以不用带有参数。
如果想要第一次调用next方法时,就能够输入值,可以在Generator函数外面再包一层。
function wrapper(generatorFunction) {
return function (...args) {
var generatorObject = generatorFunction(...args);
generatorObject.next();
return generatorObject;
};
}
const wrapped = wrapper(function* () {
var result = yield "hello";
return result;
});
wrapped().next('world');
yield*
语句
如果在Generator函数内部,调用另一个Generator函数,默认情况下时没有效果的。
"use strict"
function* foo(){
yield "a";
yield "b";
}
function* bar(){
yield "x";
foo();
yield "y";
}
for (let v of bar()){
console.log(v);
}
上面代码中,foo
和bar
都是Generator函数,在bar
里面调用foo
,是没有效果的
这个就需要用到yield*
语句,用来在一个Generator函数里面执行另一个Generator函数。
function* bar() {
yield 'x';
yield* foo();
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
yield 'a';
yield 'b';
yield 'y';
}
// 等同于
function* bar() {
yield 'x';
for (let v of foo()) {
yield v;
}
yield 'y';
}
for (let v of bar()){
console.log(v);
}
yield*
不过是for...of
的一种简写形式,完全可以用后者替代前者。
任何数据结构只要有Iterator
接口,就可以被yield*
遍历。
function* foo() {
yield 'hello';
yield* 'hello';
}
for (let v of foo()){
console.log(v);
}
如果被代理的Generator函数有return
语句,那么可以向代理它的Generator函数返回数据。
function* foo(){
yield 2;
yield 3;
return "foo";
}
function* bar(){
yield 1;
var v = yield *foo();
console.log("v:",v);
yield 4;
}
for (let v of bar()){
console.log(v);
}
作为对象属性的Generator
函数
let obj = {
* myGeneratorMethod(){
yield 1;
}
}
上面代码中,myGeneratorMethod
属性前面有一个星号,表示这个属性是一个Generator
函数
它的完整形式如下,与上面的写法是等价的。
let obj = {
myGeneratorMethod:function*(){
yield 1;
}
}
Generator函数的this
Generator函数总是返回一个遍历器,ES6规定这个遍历器是Generator函数的实例,也继承了Geenraotr函数的prototype
对象上的方法。
function* g() {}
g.prototype.hello = function () {
return 'hi!';
};
let obj = g();
obj instanceof g; // true
obj.hello(); // 'hi!'
上面代码表明,Generator
函数g返回的遍历器obj
,是g
的实例,而且继承了g.prototype
。
但是,如果把g
当作普通的构造函数,并不会生效,因为g
返回的总是遍历器对象,而不是this
对象。
function* g(){
this.a = 11;
}
let obj = g();
obj.a;//undefined
Generator与状态机
Generator是实现状态机的最佳结构。下面的clock函数就是一个状态机。
var ticking = true;
var clock = function(){
if(ticking) console.log("Tick!");
else console.log("Tock!");
ticking = !ticking;
}
上述代码的clock函数一共有两种状态(Tick和Tock),每运行一次,就改变一次状态。
var clock = function*(){
while(true){
console.log("Tick!");
yield;
console.log("Tock!");
yield;
}
}