理解ES6中的Generator

in Notes with 0 comment

之前一直看别人写的教程和文档,轮到自己写的时候发现很纠结。所以这篇文章应该算是读书笔记,同时也算是复习,简单加入了自己的一点点理解。

什么是生成器

Generator,我们又叫生成器,是ES2015中最为强悍的一个新特性。生成器在CLU语言和C#语言中被称为迭代器(Iterator),在Rudy语言中被称为枚举器(Enumerator)。

生成器的主要作用是:通过一段程序,持续迭代或枚举出符合某个公式或算法的有序数列中的元素。

例子:

斐波那契数列的前两项是0和1,从第三项开始遵循如下公式:

$$
F_n = F_{n-1} + F_{n-2} (n {\ge} 3)
$$

那么一般地,我们是这样实现的:

const fibonacci = [0,1]
const n = 10

for(let i = 2; i < n-1; ++i){
    fibonacci.push(fibonacci[i-1] + fibonacci[i-2])
}
console.log(fibonacci)

这种情况下,是需要确定一个数量,例如上面的n=10,来获取相应的数列,如果要按需获取,这里的方法之一就是用生成器了。

function* fibo(){
    let a = 0
    let b = 1

    yield a
    yield b

    while(true){
        let next = a + b
        a = b
        b = next
        yield next
    }
}

let fibonacci = fibo()

for(var i = 0; i < 10; i++){
    console.log(fibonacci.next().value)
}

从上面代码里,新的地方有function*yield以及为什么while(true)不会导致死循环

生成器函数与对象

这跟变量声明与赋值有点类似

首先*是生成器函数的标识符,有了它程序才知道它是生成器

function* fibo(){}就是声明了一个fibo()的生成器函数

然后我们定义,对象实例化生成器就是let fibonacci = fibo()这句

当然,我们也可以用表达式去定义

let fibonacci = function* (){}

yield语句

yield语言跟return语句有点类似,区别在于执行的时候并没有退出函数体,而是切出当前函数的运行,同时也像return一样可以带一个东西到函数外

上面的斐波那契数列生成器通过yield语句,将每一次的运算结果切出执行对象,然后将值带到主线程上

yield语句可以将一个东西带到主线程,那么主线程也能通过生成器对象的方法将一个对象带回到生成器的执行对象中

const inputValue = yield outputValue

主线程通过.next(val)方法将inputValue带到生成器的执行对象中

如何食用

三个步骤:构建生成器函数、启动生成器、允许生成器内容

跟上面的斐波那契数列例子是类似的,不再补充

生成器类语法与内容

操作方法内容
generator.next(value)获取下一个生成器切出的状态,第一次执行则是第一个切出状态
generator.throw(error)向当前生成器执行对象抛出一个错误并终止执行
generator[@@iterator]为生成器提供实现迭代对象的方法,使其能被for-of执行

其中,.next(value)会返回一个状态对象,包含当前生成器的运行状态与所返回的值,如

{
    value : Any;
    done : Boolean
}

生成器执行对象会不断检查生成器的状态,一旦遇到最后一个yield语句或第一个return语句就会进入终止状态,done属性由false变为true

因为生成器对象自身也是个可迭代的对象,可以使用for-of,例如上面斐波那契数列生成器这段代码

for(var i = 0; i < 10; i++){
    console.log(fibonacci.next().value)
}

可以修改为下面

for (let n of fibonacci()) {
  if (n > 10) break
  console.log(n)
}

类型检测

生成器函数检测

function isGeneratorFunction(fn){
    if (Symbol && Symbol.toStringTag){
        return fn[Symbol.toStringTag] === 'GeneratorFunction'
    }

    const genFn = (function* (){}).constructor

    return fn instanceof genFn
}

生成器对象检测

function isGenerator(obj){
    if (Symbol && Symbol.toStringTag){
        return obj[Symbol.toStringTag] === 'Generator'
    }else if(obj.toString){
        return obj.toString() === '[object Generator]'
    }

    return false
}

生成器嵌套

通过yield*语法实现,例子如下

function* foo(){
    yield 1
    yield 2
}

function bar(){
    yield* foo()
    yield 3
    yield 4
}

for(const n of bar()) console.log(n)

这次又写了很久···

Responses