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

yield 迭代篇

可能是因为TJ大神的co库,大家上来理解yield,都会首先向异步的方向去思考。其实yield本身最本初的目的,就是做迭代,这个上一篇有详细说明。

这篇专注yield在迭代上的功能,讲一下JS中的细节,并实现一个小型的帮助库,让我们可以写下下面这样的代码

var data = [];
for(var i = 1;i<=100;i++){
    data.push(i);
}

var arr = iter(data)
            .where( function(v){return v%2==0} )
            .take(10)
            .map( function(v){return v*v} )
            .toArray();

熟悉函数式的同学,应该可以猜出上面代码的含义: 对从1到100的数组,先取出其中的所有偶数,然后再取出所有其中的前10个,然后再计算其平方,然后转成数组。

最后的结果,arr:[4,16,36…400]

不要纠结这个变态的需求,从实现上,上面的代码简洁明了(如果加上lambda表达式的支持,会更简洁),而且只对原始的data迭代了一次。

本次要讲的内容有

  • 1.GeneratorFunction
  • 2.把数组包装成GeneratorFunction
  • 3.层层包装
  • 4.链式调用

1.GeneratorFunction

function* oneTwo(){
    yield 1;
    yield 2;
}

var gen = oneTwo();

此时 gen 是什么东东。调试一下,可以看到

enter image description here

其中next方法就是我进行迭代方法了。

var one = gen.next()

此时one中,有两个属性很重要:

  • 一个是value,也就是yield 后面的值,这里是1
  • 一个是done,变成迭代是否完成,这里迭代没完成,one.done 为false

下继续next

var two = gen.next();
//two.value:2,two.done:false

var three = gen.next()//
//three.value:undefined,three.done:true

gen.next() //Uncaught Error: Generator has already finished 

迭代完成。除了我们yield两个外,第三次next后,还返回一个相当于哨兵变量的对象,done为true,而值不是我们需要的。如果再调用next,那么会出现错误,因为生成器已经结束。

2.把数组包装成GeneratorFunction

var _1to10 = [1,2,3,4,5,6,7,8,9,10];

function* wrap(arr){
    for(var i = 0;i<arr.length;i++){
        yield arr[i]; // ---(1)----
    }
}

var gen = wrap(_1to10);// ---(2)----

while(true){
    var _g = gen.next(); // ---(3)----
    if( _g.done) break;
    console.log(_g.value);
}

上面的代码做的事:把1到10打印到控制台。是不是无比蛋疼,但是请你务必看下去。

调试一下,会发现,代码的执行顺序是 (2) (3) (1) (3) (1) (3) (1)...

也就是说,执行(2)的时候,实际没有进入wrap函数当中,而是直接返回了一个GenaratorFunction(虽然可能会刺激到一些同学,但是下面会把它简写为 GF 。。)只有当执行gen.next的时候,才会去真正迭代原数组。这就是延迟加载的特性了。利用这个特性,其实我们可以对GF进行层层包装。

3.层层包装

//这段代码独立运行

var _1to10 = [1,2,3,4,5,6,7,8,9,10];

function* wrap(arr){
    for(var i = 0;i<arr.length;i++){
        yield arr[i]; // ---(1)----
    }
}

//f:filter function,T -> boolean
//对序列进行过滤
function* where(gen,f){
    var _g;
    while(true){
        _g = gen.next();//------(4)-------
        if(_g.done)break;
        if( f(_g.value) ){
            yield _g.value;
        }
    }
}

//对序列的值进行映射
function* map(gen,f){
        var index = 0, _g,done,value;
        while(true){
            _g =gen.next();//------(5)-------
            if(_g.done) break;
            yield f(_g.value,index++);
        }
}


var gen1 = wrap(_1to10);
var gen2 = where(gen1,function(v){return v%2==0});
var gen3 = map(gen2,function(v){return v*v});

while(true){
    var _g = gen3.next(); // ---- (6)----
    if( _g.done) break;
    console.log(_g.value);//output:4,16,36,64,100
}

这代码的执行顺序是 6145 6145。。。实际上,就是next中调用next再调用next,套了三层next

gen3.next(){
    //....
    gen2.next(){
        //....
        gen1.next();
        //....
    }
    //....
}

但是别管套多少层,只对原数组进行了一次遍历,性能是有保证的。

4.链式调用

上面的代码生成了三个gen,然后一个个调用,显然丑爆了,如果能链式调用多好。其实反复的更新gen就行。代码有点多,但是思路还是挺清晰的

  • 1.xxImpl 对已有的gen进行在包装,返回新的gen,
  • 2.对外的API(这里为演示方便,没有进行封装):xx , 使用xxImpl的返回值 更新this.gen,然后return this,以支持链式调用

    //本次的最终代码,独立可运行
    function* wrap(arr){
        for(var i = 0;i<arr.length;i++){
            yield arr[i]; // ---(1)----
        }
    }
    
    function iter(arr){
        return new Iterator(arr);
    }
    
    function Iterator(arr){
        this.gen = wrap(arr);
    }
    
    Iterator.prototype = {
        where:where,
        map:map,
        toArray:toArray,
    }
    
    function* mapImpl(gen,f){
        var _g;
        while(true){
            _g =gen.next();
            if(_g.done) break;
            yield f(_g.value);
        }
    }
    
    function map(f){
        var gen = mapImpl(this.gen,f);
        this.gen =gen;
        return this;
    }
    
    function* whereImple(gen,f){
        var index = 0, _g,done,value;
        while(true){
            _g = gen.next();
            if(_g.done) break;
            value = _g.value;
            if(f(value,index++)){
                yield value;
            }
        }
    }
    
    function where(f){
        var gen = whereImple(this.gen,f);
        this.gen = gen;
        return this;
    }
    
    function toArray(){
        var arr = [];
        var _g;
        var gen = this.gen;
        while (true){
            _g=gen.next();
            if(_g.done)break;
            arr.push(_g.value);
        };
        return arr;
    }
    

然后我们就可以这样调用了

var _1to10 = [1,2,3,4,5,6,7,8,9,10];
var arr =iter(_1to10)
            .where(function(v){return v%2==0})
            .map(function(v){return v*v})
            .toArray();
console.log(arr); // [4, 16, 36, 64, 100] 

美中不足的是,没有lambda表达式,不然会好看很多。 我在 github 上写一个更完整的实现,支持以下方法,

map,where,concat,take

sort,groupby

reduce,max,min,sum,avg,stat

贴2个例子,有兴趣的同学可以去看一下


    //sort
    //对年龄大于20的人,按年龄进行排序
    var person = [{first:'god',second:'pig',age:25,male:'m'},
                {first:'wang',second:'er',age:26,male:'m'},
                {first:'zhang',second:'san',age:18,male:'m'},
                {first:'li',second:'si',age:19,male:'m'},
                {first:'god',second:'pig',age:21,male:'m'},
                {first:'god',second:'cat',age:22,male:'m'}];

    var arr= iter(person)
                .where(function(v){return v.age>20})
                .sort(function(v1,v2){return v1.age-v2.age})
                .toArray();
    //arr:[{..age:21..} , ... , {..age:26}...]

    //groupBy
    //找出有3本以上书的类别(category)
    var books = [{'name':'js1','category':'js','price':10}
            ,{'name':'js2','category':'js','price':20}
            ,{'name':'js3','category':'js','price':30}
            ,{'name':'js4','category':'js','price':40}
            ,{'name':'node1','category':'node','price':50}
            ,{'name':'node2','category':'node','price':60}
            ,{'name':'node3','category':'node','price':70}
            ,{'name':'other1','category':'other','price':80}
            ,{'name':'other2','category':'other','price':90}]


    var arr = iter(books)
                .groupBy('category')
                .where(function(v){return v.values.length>=3})
                .toArray();

    /*arr=[ {key:'js' , values:[{name:'js1'.....}, ...] }
                 {key:'node' , values:[{name:'node1'.....},...]
    ]*/
评论
发表评论
4年前

@寸志 寸老师居然来顶我,荣幸

4年前

这篇文章很不错

PUBLISHED IN
深入理解 Yield

从原理上,讲讲Yield

我的收藏