yield 原理篇
发布在深入理解 Yield2013年12月29日view:27254
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

新版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就行,就好像函数在暂停后,继续执行。

这只是原理,还需要看大量的示例,注意JS的实现细节,并发挥自己的想象力,才能用好这个黑魔法(~~

评论
发表评论
2个月前

肯定错了呀, 这是Java的写法

java:

for(int element : array){
    System.out.println(i);
}

> javascript:

for(var i in array){ console.log(i); }

2个月前

去他妹的, 什么辣鸡 肯定错了呀, 这是Java的写法 java:

for(int element : array){
    System.out.println(i);
}

javascript:

for(var i in array){ console.log(i); }

2个月前

肯定错了呀, 这是Java的写法 java:

        System.out.println(i);
    }

javascript:

for(var i in array){ console.log(i); }

2个月前

肯定错了呀, 这是Java的写法 java:

    System.out.println(i);
}

javascript:

for(var i in array){ console.log(i); }

4个月前
添加了一枚【评注】:这个for循环是不是语法错误了?
6个月前

很牛,很牛的感觉。

1年前

有种java的错觉

2年前

兄台解释简洁明了,深入浅出,我以前对这个迷迷糊糊,看了这篇文章茅塞顿开

2年前

兄台解释简洁明了,深入浅出,我以前对这个迷迷糊糊,看了这篇文章茅塞顿开

4年前

不错,1024

4年前

最近也在看这个,赶项目还要招人,,,,忙死没空看。。。。发邮件给你了,加油少年看好你。

PUBLISHED IN
深入理解 Yield

从原理上,讲讲Yield

我的收藏