ES6之生成器Generator

1. 什么是生成器Generator

生成器Generator在异步流程中,它可以控制我们的一个异步流程。
我们先看看生成器的一个基础语法:
function * fn(){} 注意:*放在开头或者中间或者结尾都是可以的。
生成器与普通函数不同的第一点就是:普通函数使用 function 声明,而生成器函数使用 function* 声明。
接下来我们再来看几行代码:

1
2
3
4
5
6
7
8
9
10
function * fn(){
yield "xiaoming"; // 在这里yield相当于一个开关,到哪里去暂停。yield语句仅仅是在Generator里面好使,如果你写在普通函数或者其他语句中,它就会报错。
yield "xiaoli";
yield "xiaohong";
}
const people = fn(); // 当fn函数执行完后,会返回一个迭代器iterator。所以此时将fn()返回的值用一个变量people接收。
people.next(); // {value: "xiaoming", done: false}
people.next(); // {value: "xiaoli", done: false}
people.next(); // {value: "xiaohong", done: false}
people.next(); // {value: undefined, done: true}

生成器与普通函数不同的第二点就是:在生成器函数内部,有一种类似 return 的语法:关键字 yield。二者的区别是,普通函数只可以 return 一次,而生成器函数可以 yield 多次(当然也可以只 yield 一次)。next()相当于开按钮,yield相当于关按钮。
普通函数,如果没有return语句就会从头到尾执行。

普通函数和生成器函数之间最大的区别,普通函数不能自暂停,生成器函数可以。

2. 生成器Generator的应用例子

例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
function * fn(){
console.log("A");
yield "xiaoming";
console.log("B");
yield "xiaoli";
console.log("C");
yield "xiaohong";
}
const people = fn();
people.next(); // A {value: "xiaoming", done: false}
people.next(); // B {value: "xiaoli", done: false}
people.next(); // C {value: "xiaohong", done: false}
people.next(); // {value: undefined, done: true}

例2:
在做例2之前,我们需要先注意一点:如果没有在next()方法里面传入参数的话,我们整体的yield语句就是一个undefined。如果你想把yield语句的值更改的话,就通过next()方法传入参数。

1
2
3
4
5
6
7
8
9
function * fn(x){
var y = yield(x+2);
var z = yield(y/3);
return (x+y+z);
}
const it = fn(4);
console.log(it.next()); // {value: 6, done: false}
console.log(it.next()); // {value: NaN, done: false}
console.log(it.next()); // {value: NaN, done: true}
  1. 当执行第一个next()方法的时候,碰到的是一个赋值语句,把右边的值给左边,但是右边的值碰到了yield语句就暂停了。所以就把yield后面的值(x+2)给value,就是6;
  2. 当执行第2个next()方法的时候,现在可以完成赋值语句了,我们应该把y赋值成yield(x+2)整个的语句,它整个语句的值是一个undefined。然后再执行z的赋值语句,但是遇到第二个yield语句就暂停了,value就输出”undefined/3”=NaN;
  3. 当执行第三个next()方法的时候,由于yield(y/3)中整个的语句没有传值进来,那么它的整个语句的值是一个undefined,即z就为一个undefined,所以x+y+z=4+undefined+undefined=NaN,所以value的值为NaN。由于一个return语句,表示当前已经执行完了,所以done为true。

例3:
我们接下来再做一个例子,是在next()方法中传入一个值。

1
2
3
4
5
6
7
8
9
function * fn(x){
var y = yield(x+2);
var z = yield(y/3);
return (x+y+z);
}
const it = fn(4);
console.log(it.next()); // {value: 6, done: false}
console.log(it.next(6)); // {value: 2, done: false}
console.log(it.next()); // {value: NaN, done: true}
  1. 当执行第2个next()方法的时候,执行的是y的赋值语句,整体yield(x+2)语句等于next(6)传入过来的参数6,那么y=6。接着执行z的赋值语句,但是碰到了右边的yield语句又暂停了,所以value的值为y/3 = 6/3 = 2。
  2. 当执行第3个next()方法的时候,执行z的赋值语句,由于next()方法中没有传值,所以z的值为undefined。所以x+y+z=4+6+undefined = NaN。

如果想要z有值的话,就在第三个next()方法中传入一个值,如it.next(10),那么第三next()方法执行完后打印出来为:x+y+z=4+6+10=20即{value: 20,done: true}

3. erator函数执行返回generator对象,只是说它的用法和iterator一样

for of循环:
generator函数执行返回generator对象,只是说它的用法和iterator一样,iterator可以通过”for of”直接循环出来,不用我们手动.next()执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var arr = [1,2,3];
function * fn(){
console.log("A");
yield "xiaoming";
console.log("B");
yield "xiaoli";
console.log("C");
yield "xiaohong";
}
const people = fn(); // 是一个iterator
for(var person of people){
console.log(person);
}

// 控制台会打印出如下所示内容:
// A
// xiaoming
// B
// xiaoli
// C
// xiaohong

4. 给普通对象写一个自己的Symbol.iterator

由于普通的对象是没有Symbol.iterator的,如果我们用for of去循环是不好使的。因此我们可以自己写一个Symbol.iterator,然后去循环for of。

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {a: 1};
obj[Symbol.iterator] = function * (){ // 注意:Generator函数一执行完就会返回一个迭代器
yield 1;
yield 2;
return 3; // 如果想让控制台把3也打印出来的话,只需要把return改为yield
}
for(var prop of obj){ // 它会自动去找obj上面的Symbol.iterator,然后给它执行,执行完之后就是一个iterator
console.log(prop);
}

// 控制台打印出如下内容:
// 1
// 2