Geek-James/Blog

4.ES6详解-Generator

Geek-James opened this issue · 1 comments

1.基本概念

  • Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。

  • 执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

  • Generator 本质是一个遍历器生成函数

  • 形式上,Generator 函数是一个普通函数,但是有两个特征。

一是,function关键字与函数名之间有一个星号; function*()

二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。

    function* generator(){
        yield 1;
        yield 2;
        yield 3;
        return 'ending';
    }
    let hw = generator();

Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。
不同的是:
调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。

下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个的yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。

hw.next()
// { value: 1, done: false }

hw.next()
// { value: 2, done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }

总结一下:

调用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield表达式后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束.

另外需要注意:yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。

(function (){
  yield 1;
})()
// SyntaxError: Unexpected number

通过yield* 来剥离复杂数据

{
    let arr = [1,[2,3],[[4,5,6],7],[[8],[[9],[10]]]];
    let flat = function* (a) {
        let length = a.length;
        for (let i=0;i<length;i++){
            let item = a[i];
            if(typeof item !== 'number') {
                yield* flat(item);
            } else {
                yield item;
            }
        }
    };
    for (var f of flat(arr)) {
       console.log(f); // 1,2,3,4,5,6,7,8,9,10
    }
    
}

与 Iterator 接口的关系

任意一个对象的Symbol.iterator方法,等于该对象的遍历器生成函数,调用该函数会返回该对象的一个遍历器对象.
由于 Generator 函数就是遍历器生成函数,因此可以把 Generator 赋值给对象的Symbol.iterator属性,从而使得该对象具有 Iterator 接口。

{
    let myInterable = {};
    myInterable[Symbol.iterator] = function* (){
        yield 1;
        yield 2;
        yield 3;
    };
    for (const value of myInterable) {
        console.log(value);  // 1,2,3
    }
    console.log([...myInterable]); // [1,2,3]
    
}

next 方法的参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* f() {
  for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
  }
}

var g = f();

g.next() // { value: 0, done: false }
g.next() // { value: 1, done: false }
g.next(true) // { value: 0, done: false }

另外一个例子:

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 }

如果向next方法提供参数,返回结果就完全不一样了。上面代码
第一次调用b的next方法时,x的值为5,返回x+1的值6;
第二次调用next方法,将上一次yield表达式的值设为12,因此y等于24,返回y / 3的值8;
第三次调用next方法,将上一次yield表达式的值设为13,因此z等于13,这时x等于5,y等于24,所以return语句的值等于42。

下面是一个利用 Generator 函数和for...of循环,实现斐波那契数列的例子。

{
    function* fiboncci() {
        let [prev,curr] = [0,1];
        for(;;){
            yield curr;
            [prev,curr] = [curr,prev + curr];
        }
    }
    let array = [];
    for (let n of fiboncci()) {
        if(n>1000) break;
        array.push(n);
    }
     console.log(array); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987]
}

Generator.prototype.return()

Generator 函数返回的遍历器对象,还有一个return方法,可以返回给定的值,并且终结遍历 Generator 函数。

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,以后再调用next方法,done属性总是返回true。

注意点:
如果 Generator 函数内部有try...finally代码块,且正在执行try代码块,那么return方法会推迟到finally代码块执行完再执行。

function* numbers () {
  yield 1;
  try {
    yield 2;
    yield 3;
  } finally {
    yield 4;
    yield 5;
  }
  yield 6;
}
var g = numbers();
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.return(7) // { value: 4, done: false }
g.next() // { value: 5, done: false }
g.next() // { value: 7, done: true }

yield* 表达式