es6之Generator函数

199 阅读5分钟

什么是Generator函数

Generator函数,可以理解为一种状态机,封装了多个内部状态。是一个遍历器生成器,返回遍历器对象(即Generator 函数的内部指针)。

Generator函数和普通函数区别:

  • 1、函数function和函数名称中间有个星号
  • 2、调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象
  • 3、函数内部使用yield表达式定义内部状态,普通函数不能使用yield表达式。可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数
  • 4、必须调用遍历器对象的next方法,使得指针移向下一个状态
function* foo(){
    console.log('hello');
}
var bar = foo();

bar.next();
bar.next();

//'hello'

一个普通函数中使用yield表达式,结果产生一个句法错误

(function(){
    yield 1;
})(); // 报错

(function*(){
    yield 1;
})();//ok
function* foo() {
    console.log('start');
    yield 1;
    console.log('middle');
    yield 2;
    console.log('end');
    return 3;
}
var bar = foo();
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());

//start
//{ value: 1, done: false }
//middle
//{ value: 2, done: false }
//end
//{ value: 3, done: true }
//{ value: undefined, done: true }

Generator函数的运行流程:

  • 第 1 次调用next,输出start,遇到第一个yield,返回{ value: 1, done: false }
  • 第 2 次调用next,从上一次yield停下来的地方,直到下一个yield表达式。输出middle,遇到第二个yield,返回{ value: 2, done: false }
  • 第 3 次调用next,从上一次yield停下来的地方,遇到return。输出end,返回{ value: 3, done: true }
  • 第 4 次调用next,返回{ value: 3, done: true }。如果还有next调用都返回这个结果

yield 和return

区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。

yield表达式本身没有返回值,或者说总是返回undefined。yield表达式如果用在另一个表达式之中,必须放在圆括号里面

function* foo() {
    //在另一个表达式中,yield表达式必须加上圆括号
    console.log( 'hello' +  (yield 'world'));
}
var bar = foo();
console.log(bar.next());
console.log(bar.next());

//{ value: 'world', done: false }
//helloundefined
//{ value: undefined, done: true }

yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号

function fun(a){
    console.log('a:'+ a);
}
function* foo() {
    fun(yield 'hello', yield 'world'); //函数参数
    let value = yield; //表达式右侧
}
var bar = foo();
console.log(bar.next()); 
console.log(bar.next());
console.log(bar.next());
console.log(bar.next());

//{ value: 'hello', done: false }
//{ value: 'world', done: false }
//a:undefined
//{ value: undefined, done: false }
//{ value: undefined, done: true }

与Iterator接口

任意一个对象的Symbol.iterator方法等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象。

Generator 函数执行后,返回一个遍历器对象。该对象本身也具有Symbol.iterator属性,执行后返回自身。

function* foo() {
    yield 1;
}
var bar = foo();
bar === bar[Symbol.iterator]() //true

Generator 函数赋值给Symbol.iterator属性,从而使得myIterable对象具有了 Iterator 接口,可以被...运算符遍历了

var myIterable = {};
myIterable[Symbol.iterator] = function* foo() {
    yield 1;
    yield 2;
};
[...myIterable] //[1, 2]

next方法参数

next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值

function* foo() {
    let a = 10;
    let b = yield 10 + a;
    yield b + 10;
}

var bar = foo();

bar.next();    //{ value: 20, done: false }
bar.next(20); //{ value: 30, done: false }
bar.next();   //{ value: undefined, done: true }

第二个next参数20作为,第一次yield的返回值,所有b = 20 ,所以第二个yield表达式为值为30

Generator 函数从暂停状态到恢复运行,它的上下文状态(context)是不变的。通过next方法的参数,就有办法在 Generator 函数开始运行之后,继续向函数体内部注入值。

for...of循环

for...of循环遍历Iterator对象,不再需要调用next方法

function* foo() {
  yield 1;
  yield 2;
  return 3;
}

for (let v of foo()) {
  console.log(v);
}//1 2

需要注意,一旦next方法的返回对象的done属性为true,for...of循环就会中止,且不包含该返回对象,所以上面代码的return语句返回的3,不包括在for...of循环之中

for...of循环、扩展运算符(...)、解构赋值和Array.from方法内部调用的,都是遍历器接口。这意味着,它们都可以将 Generator 函数返回的 Iterator 对象,作为参数。

使对象类型数据也能使用for...of 第一种

function* foo() {
    let keys = Object.keys(this);

    for (let key of keys) {
        yield [key, this[key]];
    }
}
var bar = { name: 'li', age: 20};
bar[Symbol.iterator] = foo;

for (let value of bar) {
    console.log(value);
}
//[ 'name', 'li' ]
//[ 'age', 20 ]

第二种实现

function* foo(obj) {
    let keys = Object.keys(obj);
    for(let key of keys){
        yield [key, obj[key]];
    }
}
var bar = {name: 'li', age: 20, sex: 'man'};

for (let value of foo(bar)){
    console.log(value);
}

throw方法

generator.throw()抛出错误先看内部又没有捕获,如果内部没有捕获,就外部捕获。如果都没有捕获,那么程序将报错,直接中断执行。

注意区分throw方法和全局的throw

function* foo() {
    try{
        yield 1;
    } catch(error){
        console.log('error');
    }
    yield 2;
}

var bar = foo();

bar.next(); //{ value: 1, done: false }
bar.throw();//{ value: 2, done: false }

throw new Error('xxx');
  • bar.throw方法抛出的错误要被内部捕获,前提是必须至少执行过一次next方法
  • bar.throw方法被捕获以后,会附带执行下一条yield表达式,也就是说,会附带执行一次next方法
  • throw命令与g.throw方法是无关的,两者互不影响
  • 一旦 Generator 执行过程中抛出错误,且没有被内部捕获,就不会再执行下去了。如果此后还调用next方法,返回{ value: undefined, done: true }

return方法

如果return方法传有参数就当做返回对象的value值。往后再调用next方法都返回{ value: undefined, done: true }

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 }

如果Generator中有try...finaly,调用return()方法后,就开始执行finally代码块,不执行try里面剩下的代码了,然后等到finally代码块执行完,再返回return()方法指定的返回

function* gen() {
    try {
        yield 1;
        yield 2;
    } finally {
        yield 3;
        yield 4;
    }
}

var g = gen();

g.next();//{ value: 1, done: false }
g.return('foo');//{ value: 3, done: false }
g.next(); //{ value: 4, done: false }
g.next(); //{ value: 'foo', done: true }
g.next(); //{ value: undefined, done: true }

yield*表达式

在 Generator 函数内部,调用另一个 Generator 函数。

function* foo() {
    yield 1;
    yield* bar();
    yield 2;
}
function* bar() {
    yield 3;
    yield 4;
}

var g = foo();

[...g];//[ 1, 3, 4, 2 ]