Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

4.ES6详解-Generator #16

Open
Geek-James opened this issue Jul 18, 2019 · 1 comment
Open

4.ES6详解-Generator #16

Geek-James opened this issue Jul 18, 2019 · 1 comment
Labels

Comments

@Geek-James
Copy link
Owner

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* 表达式

@Geek-James Geek-James changed the title ES6详解-Generator 4.ES6详解-Generator Jul 18, 2019
@Geek-James Geek-James added the ES6 label Jul 19, 2019
@cloudcome
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants