HTML笔记 ·

generator生成器

generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。

我们先来看一下函数的写法,一个函数是一段完整的代码,调用一个函数就是传入参数,然后返回结果

function foo(x) {
    return x + x;
}
var r = foo(1); // 调用foo函数

函数在执行过程中,如果没有遇到return语句(函数末尾如果没有return,就是隐含的return undefined;),控制权无法交回被调用的代码。

generator和函数类似,声明的时候使用function* 来声明,内部通过yeild来阻断代码执行,并返回相应的值,在次执行next时继续后面代码执行。

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

调用的时候示例

var test = foo(1);
test.next();//{value: 2, done: false}
test.next();//{value: 3, done: false}
test.next();//{value: 4, done: true}
test.next();//{value: undefined, done: true}

yield介绍

yield关键字使生成器函数执行暂停,yield关键字后面的表达式的值返回给生成器的调用者。它可以被认为是一个基于生成器的版本的return关键字。

yield关键字实际返回一个IteratorResult对象,它有两个属性,value和done。value属性是对yield表达式求值的结果,而done是false,表示生成器函数尚未完全完成。

一旦遇到 yield 表达式,生成器的代码将被暂停运行,直到生成器的 next() 方法被调用。每次调用生成器的next()方法时,生成器都会恢复执行,直到达到以下某个值:

yield,导致生成器再次暂停并返回生成器的新值。 下一次调用next()时,在yield之后紧接着的语句继续执行。
throw用于从生成器中抛出异常。这让生成器完全停止执行,并在调用者中继续执行,正如通常情况下抛出异常一样。
到达生成器函数的结尾;在这种情况下,生成器的执行结束,并且IteratorResult给调用者返回undefined并且done为true。
到达return 语句。在这种情况下,生成器的执行结束,并将IteratorResult返回给调用者,其值是由return语句指定的,并且done 为true。
如果将可选值传递给生成器的next()方法,则该值将成为生成器当前yield操作返回的值。

在生成器的代码路径中的yield运算符,以及通过将其传递给Generator.prototype.next()指定新的起始值的能力之间,生成器提供了强大的控制力。

在使用yield阻断程序运行时需要注意一点yield 使用的地方必须是能够返回函数调用的位置,比如说

function* foo(x) {
   setTimeout(()=>{
        yield x;
   },1000)
}

如此写必然会产生报错,因为在异步函数内返回是无法返回到函数调用者身上。

另外在不能结束循环的位置使用也是无法实现效果,例如map

function* foo(x) {
  var x = [1,2,3,4,5,6];
  x.map(item=>{
    yield item
  })
}

这种方式和在异步函数中写效果是一样的,都会报错,可以将map改为for循环或者while循环即可。
例如要编写一个产生斐波那契数列的函数,可以这么写:

function* fib(max) {
    var
        t,
        a = 0,
        b = 1,
        n = 0;
    while (n < max) {
        yield a;
        [a, b] = [b, a + b];
        n ++;
    }
    return;
}

调用的时候可以使用for of来调用

for (var x of fib(10)) {
    console.log(x); // 依次输出0, 1, 1, 2, 3, ...
}

generator和普通函数相比,有什么用?

因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个generator就可以实现需要用面向对象才能实现的功能。

比如一个自增函数,如果用普通的函数来写的话

var current_id = 0;

function next_id() {
    current_id ++;
    return current_id;
}

由于函数无法保存状态,故需要一个全局变量current_id来保存数字。

然而用generator来写就比较简单了,不需要声明全局变量

function next_id() {
    var current_id = 0;
    do{
        yield current_id;
        current_id++
    }while(true)
}

这样每次调用next都会生成一个id,同时不用声明全局变量,减少全局变量污染的情况。

参与评论