zoukankan      html  css  js  c++  java
  • Prototype源码浅析——Enumerable部分(三)

    现在来看Enumerable剩下的方法

    toArray | size | inspect

    inject | invoke | sortBy | eachSlice | inGroupsOf | plunk | zip

     

    前面说过map的原理,不管原来的集合是什么,调用map之后返回的结果就是一个数组,其中数组的每一项都是经过interator处理了的,如果不提供interator那么默认使用Prototype.K,此时的作用很明显,返回的结果就是原来集合的数组形式。原来的集合中length属性为多少,返回结果数组的length就是多少。

    这个特殊情况被作为一个方法独立出来,叫做toArray:

      function toArray() {
    return this.map();
    }

    另一个size方法是返回上面那个数组的长度:

      function size() {
    return this.length;
    }

    至于inspect,前面在Object和Strng部分见过好几次了,这里调用Array的inspect方法[Array部分后面分析,不过inspect这个方法太熟悉了,也没必要说了]。

      function inspect() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
    }

    另外,对于集合来说,另一个操作是分组。Enumerable中对应的方法是eachSlice 和 inGroupsOf。

    先抛开源码,单独来实现这个方法,需要的一个参数是每一个分组的长度(叫做number),如果最后一组长度不够,就保留最后一组的实际长度。

    具体实现步骤:

    第一、检测number的值,如果number小于1,那么肯定是非法字符,直接返回原来的集合。

    第二、将所操作集合转变为数组A,转变方法就是在原来的集合上面调toArray方法。

    第三、数组A分组有原生的方法slice,循环调用即可。

    按照上面的步骤,可得到下面的实现:

        Enumerable.eachSlice = function(number){
    if(number < 1){ //第一步
    return this;
    }
    var array = this.toArray();//第二步
    var index = 0;
    var results = [];
    while(index < array.length){//第三步
    results.push(array.slice(index,index + number));
    index += number;
    }
    return results;
    };



    对应到具体的源码实现中,index += number;这一步被挪到while的条件中,因此,为了保证循环从0开始,index的初始值被设为-number;另外,和其他的方法一样,eachSlice也提供了iterator, context两个参数,作用依旧不变,所以最后的结果变成:

      function eachSlice(number, iterator, context) {
    var index = -number, slices = [], array = this.toArray();
    if (number < 1) return array;
    while ((index += number) < array.length)
    slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context); //collect就是map
    }



    例子:

    console.log([1,2,3,4,5].eachSlice(2));//[[1,2],[3,4],[5,6]]



    至于inGroupsOf方法,则是对eachSlice的一个补充而已。eachSlice最后一组长度不够,就保留最后一组的实际长度,在inGroupsOf最后一组长度不够,会用指定的填充符填充(默认填充为null)。因此只要提供iterator函数就可以了:

        Enumerable.inGroupsOf = function(number, fillWith) {
    fillWith = typeof fillWith == 'undefined' ? null : fillWith;//源码中这里使用的是Object.isUndefined
    return this.eachSlice(number, function(slice) {
    while(slice.length < number){
    slice.push(fillWith);
    }
    return slice;
    });
    }



    下面看inject,先看手册说明:

    inject(accumulator, iterator[, context]) -> accumulatedValue

    根据参数 iterator 中定义的规则来累计值。首次迭代时,参数 accumulator 为初始值,迭代过程中,iterator 将处理过的值存放在 accumulator 中,并作为下次迭代的起始值,迭代完成后,返回处理过的 accumulator。

    这个操作其实前面也遇到过,可以单独用each来实现,看一个数组求和的例子:

        console.dir([1,2,3,4].inject(0,function(sum,n){
    return sum + n;
    }));//10



    如果换做each实现,就是:

        var sum = 0;
    [1,2,3,4].each(function(value){
    sum += value;
    })
    console.dir(sum);//10

     

    对比上面的实现和源码中的实现,上面的实现中有一个缺陷:sum全局变量,这是不合理的。

    所以变形上面的形式,抛弃那个全局变量sum,由于each方法是固定死的,没有办法再改变,所以我们在外层再包装一个方法,并将叠加部分填进去:

        function fn(accumulator,interator,context){
    [1,2,3,4].each(function(value,index){
    accumulator = interator.call(context,accumulator,value,index);
    })
    return accumulator;
    }
    console.dir(fn(0,function(accumulator,value,index){
    accumulator += value;
    return accumulator;
    }));//10

     

    看上面的实现,需要注意的一点是,fn中第一个传入的是一个引用类型的变量,由于这一个实现方式:

    accumulator = interator.call(context,accumulator,value,index);



    那么最后返回的结果是同一个变量,是对最初传入变量的一个引用,这一点是出于性能和效率的考虑,不过有时候可能导致问题,需警惕。

     

    接下来是invoke方法,这个方法和each(map)的作用基本一致,唯一的区别是each(map)执行的是外部提供的一个方法,而invoke执行的是集合对象自身本来就存在的方法。因此,invoke的参数有且只需要有一个,就是集合对象的方法名。举个例子,我们将一个数字数组的每一项都转化为字符串:

    对比两种实现:

        var array_1 = [1,2,3,4].map(function(value){
    return value.toString();
    });
    console.log('array_1:',array_1);//["1", "2", "3", "4"]
    var array_2 = [1,2,3,4].invoke('toString');
    console.log('array_2:',array_2);//["1", "2", "3", "4"]



    由于少了一次闭包的消耗,因此invoke在效率上稍高,而且形式也简洁不少。

    具体实现:

      function invoke(method) {
    //var args = $A(arguments).slice(1); //源码实现
    var args = Array.prototype.slice.call(arguments,1);
    return this.map(function(value) {
    return value[method].apply(value, args);
    });
    }

     

    plunk方法比较简单,获取所有元素的同一个属性的值,并返回相应的数组。这个方法显然是针对普通对象来的,数组的话没有什么属性好取的:

      function pluck(property) {
    var results = [];
    this.each(function(value) {
    results.push(value[property]);
    });
    return results;
    }

     

    源码好理解,给个例子就行:

    ['hello', 'world', 'my', 'is', 'xesam'].pluck('length')// [5, 5, 2, 3, 5]

     

    另一个在其他脚本里面常见的方法是zip,这个方法不是很好理解:

    zip(Sequence...[, iterator = Prototype.K]) -> Array

    将多个(两个及以上)序列按照顺序配对合并(想像一下拉链拉上的情形)为一个包含一序列元组的数组。 元组由每个原始序列的具有相同索引的元素组合而成。如果指定了可选的 iterator 参数,则元组由 iterator 指定的函数生成。 
    我们先不考虑iterator ,来第一个实现:

        function zip(){
    var array_1 = [1,2,3];
    var array_2 = ['a','b','c'];
    var array_3 = ['x','y','z'];
    var result = [array_1].concat([array_2,array_3]);//[[1,2,3],['a','b','c'],['x','y','z']]
    return array_1.map(function(value,index){
    return result.pluck(index);
    })
    }
    console.log(zip());//[[1,'a','x'],[2,'b','y'],[3,'c','z']]

    改为Enumerable方法的形式就是:

        function zip(){
    var result = [this].concat(Array.prototype.slice.call(arguments,1));
    return this.map(function(value,index){
    return result.pluck(index);
    })
    }
    console.log([1,2,3].zip(['a','b','c'],['x','y','z']));////[[1,'a','x'],[2,'b','y'],[3,'c','z']]

    加上interator处理之后就是:

        function zip(){
    var args = Array.prototype.slice.call(arguments,0);
    var interator = function(x){ return x;} //Prototype.K
    if(args[args.length - 1].constructor == Function){
    interator = args.pop();
    }
    var result = [this].concat(Array.prototype.slice.call(arguments,1));
    return this.map(function(value,index){
    return interator.call(result.pluck(index));
    })
    }

     

     

    转载请注明来自小西山子【http://www.cnblogs.com/xesam/
    本文地址:http://www.cnblogs.com/xesam/archive/2012/01/19/2326142.html
  • 相关阅读:
    纯win32实现PNG图片透明窗体
    win32用GDI+加载png图片作为背景图
    vc 制作图片资源dll
    wav文件格式分析详解
    VC++ WIN32 sdk实现按钮自绘详解.
    PNG文件结构分析 ---Png解析
    vc/mfc获取rgb图像数据后动态显示及保存图片的方法
    ffmpeg解码流程
    移植ffmpeg到VC环境心得
    Java
  • 原文地址:https://www.cnblogs.com/xesam/p/2326142.html
Copyright © 2011-2022 走看看