yield 原理篇

时间:2022-05-07
本文章向大家介绍yield 原理篇,主要内容包括1.Iterators、Generators、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

新版JS中,yield估计是最吸引人的新功能,特别是Node出来之后,大家被异步折腾的够呛,而借助于yield 可以用比较优雅的处理异步流程。 但是yield关键字,早就出现在其他语言当中了,我知道的有python和c#。这篇从最基本的原理讲起,希望大家能更好的理解yield。 关于yield 是什么,可以看这篇介绍

1.Iterators

先从迭代器模式讲起。简单地讲,迭代器是一种遍历集合的方式。它的接口很简单,一般拥有以下三个方法就可以了。

hasNext() //集合中是否还有下一个元素next() //迭代到下一个元素reset()//重置,我见到的代码一般是抛出异常,即一般不支持多次迭代

那么我们来实现一个简单的迭代器吧

function Range(min,max){//[min,max)    return {
        cur:min,
        hasNext:function(){
            return this.cur<max;
        },
        next:function(){
            return this.cur++;
        },
        reset:function(){
            throw new Error('unsupportted operation');
        }
    }}for(var iter = Range(1,10);iter.hasNext();){
    i = iter.next();
    console.log(i);}

对于迭代的过程,一般会做一些语法糖,然后用起来就很舒服了,像下面:

for(var i : Range(1,10)){
    console.log(i);}

一个复杂的例子

function fib(){
    return {
        state :0,
        cur :0,
        prev1:-1,
        prev2:-1,
        hasNext:function(){
            return true;
        },
        //fib数列,第一个是0,第二个是1,后面就是统一的迭代公式了        next:function(){
            if(this.state==0){
                this.cur = 0;
                this.state=1;
            }else if(this.state == 1){
                this.cur =1;
                this.prev2=0;
                this.state=2;
            }else{
                this.prev1 = this.prev2;
                this.prev2 =this.cur;
                this.cur = this.prev1+this.prev2;
            }
            return this.cur;
        }
        //ignore reset funciton    }}//这是无限序列,所以改造了一下,只生成8个数var fibIter = fib();for(var i = 0;i<8;i++){     
    console.log(fibIter.next());
    if(fibIter.hasNext())
        continue;}

这个例子可以展示迭代器模式更多的特点:

  • 1.可以没有真正的集合(像Array),只要有相应的生成规则就行。这种情况下,没有内存的限制,因此可以表示无限序列
  • 2.不调用next(),迭代器不进行迭代的,因此有延迟加载的特性。
  • 3.迭代器,本质上是一个状态机,比如上面的fib,每个状态下,进行一些操作,然后进入下一个状态或维持状态不变。

Generators

迭代器模式是很常用的设计模式,但是实现起来,很多东西是程序化的;当迭代规则比较复杂时,维护迭代器内的状态,是比较麻烦的。 于是有了generator,何为generator,这里 说的很明确: Generators: a better way to build Iterators.就是实现迭代器的更好的方式,借助 yield 关键字,可以更优雅的实现上面的fib数列。下面的代码需要环境支持 JS 1.7,如何让你的环境支持1.7,请参见http://html-js.com/article/1687

function* fib2(){
    yield 0;//状态0,第一次调用next,返回0,并改变状态    yield 1;//状态1,第二次调用next,返回1,并改变状态
    var p1 = 0
        ,p2 =1
        ,cur = p1+p2;
    while(true){

        yield cur;//状态2,后面调用next,返回相应的几个,状态不在改变
        p1 = p2;
        p2 = cur;
        cur = p1+p2;
    }}var fibIter2 = fib2();for(var i =0;i<8;i++){
    console.log(fibIter2.next().value);} 

对比上面自己实现的迭代器,应该可以看出,yield 是解释器给我们提供的语法糖,但是对比上下的代码,用yield,更简洁,而且逻辑也更好懂。

**上面对于状态的描述,是我自己写的,编译器(解释器)不一定会生成一样的东西。这些原理,可以从反编译的C#代码看出来:yield的确会转化成相应的状态机。JS的实现可能不一样,但是对于理解yield的行为,是没有影响的

yield与异步

yield,怎么解决异步的问题呢。 通过上面的分析,yield之后,实际上本次调用就结束了,控制权实际上已经转到了外部调用了generator的next方法的函数,调用的过程中伴随着状态的改变。那么如果外部函数不继续调用next方法,那么yield所在函数就相当于停在yield那里了。所以把异步的东西做完,要函数继续执行,只要在合适的地方再次调用generator 的next就行,就好像函数在暂停后,继续执行。