Underscore源码解析(三)


本文同步自我的博客http://jr3.me
前两天在微博上看到SF的微博推荐了我的前两篇文章,有点意外和惊喜。作为一个菜鸟,真的是倍受鼓舞,我写博客的动力也更充足了。
没看过前两篇博客的朋友可以戳这里:Underscore源码解析(一)Underscore源码解析(二)
上一篇文章介绍了underscore的10个函数的具体实现细节,今天将继续介绍其他的函数。

_.invoke

_.invoke = function(obj, method) {
    // 调用同名方法时传递的参数(从第3个参数开始)
    var args = slice.call(arguments, 2);
    // 依次调用每个元素的方法, 并将结果放入数组中返回
    return _.map(obj, function(value) {
        return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
    });
};

这个函数依次调用集合中所有元素的同名方法,从第3个参数开始的所有参数将被传入到元素的调用方法中,最后返回一个数组,该数组存储了所有方法的处理结果

_.pluck

_.pluck = function(obj, key) {
    // 如果某一个对象中不存在该属性, 则返回undefined
    return _.map(obj, function(value) {
        return value[key];
    });
};

这个函数遍历了一个由对象列表组成的集合,并返回每个对象中的指定属性的值列表

_.max

_.max = function(obj, iterator, context) {
    // 如果集合是一个数组, 且没有使用处理器, 则使用Math.max获取最大值
    // 一般会是在一个数组存储了一系列Number类型的数据
    if(!iterator && _.isArray(obj) && obj[0] === +obj[0])
        return Math.max.apply(Math, obj);
    // 对于空值, 直接返回负无穷大
    if(!iterator && _.isEmpty(obj))
        return -Infinity;
    // 一个临时的对象, computed用于在比较过程中存储最大值(临时的)
    var result = {
        computed : -Infinity
    };
    // 迭代集合中的元素
    each(obj, function(value, index, list) {
        // 如果指定了处理器参数, 则比较的数据为处理器返回的值, 否则直接使用each遍历时的默认值
        var computed = iterator ? iterator.call(context, value, index, list) : value;
        // 如果比较值相比上一个值要大, 则将当前值放入result.value
        computed >= result.computed && ( result = {
            value : value,
            computed : computed
        });
    });
    // 返回最大值
    return result.value;
};

顾名思义,这个函数用来返回集合中的最大值, 如果不存在可比较的值, 则返回undefined

_.min

_.min = function(obj, iterator, context) {
    if(!iterator && _.isArray(obj) && obj[0] === +obj[0])
        return Math.min.apply(Math, obj);
    if(!iterator && _.isEmpty(obj))
        return Infinity;
    var result = {
        computed : Infinity
    };
    each(obj, function(value, index, list) {
        var computed = iterator ? iterator.call(context, value, index, list) : value;
        computed < result.computed && ( result = {
            value : value,
            computed : computed
        });
    });
    return result.value;
};

这个函数没有加注释,因为实现过程与max基本相同,用于返回集合中的最小值

_.shuffle

_.shuffle = function(obj) {
    // shuffled变量存储处理过程及最终的结果数据
    var shuffled = [], rand;
    // 迭代集合中的元素
    each(obj, function(value, index, list) {
        // 生成一个随机数, 随机数在<0-当前已处理的数量>之间
        rand = Math.floor(Math.random() * (index + 1));
        // 将已经随机得到的元素放到shuffled数组末尾
        shuffled[index] = shuffled[rand];
        // 在前面得到的随机数的位置插入最新值
        shuffled[rand] = value;
    });
    // 返回一个数组, 该数组中存储了经过随机混排的集合元素
    return shuffled;
};

这个函数是通过随机数, 让数组无须排列,实际上是实现了一个模拟洗牌过程的算法

_.sortBy

_.sortBy = function(obj, val, context) {
    // val应该是对象的一个属性, 或一个处理器函数, 如果是一个处理器, 则应该返回需要进行比较的数据
    var iterator = _.isFunction(val) ? val : function(obj) {
        return obj[val];
    };
    // 调用顺序: _.pluck(_.map().sort());
    // 调用_.map()方法遍历集合, 并将集合中的元素放到value节点, 将元素中需要进行比较的数据放到criteria属性中
    // 调用sort()方法将集合中的元素按照criteria属性中的数据进行顺序排序
    // 调用pluck获取排序后的对象集合并返回
    return _.pluck(_.map(obj, function(value, index, list) {
        return {
            value : value,
            criteria : iterator.call(context, value, index, list)
        };
    }).sort(function(left, right) {
        var a = left.criteria, b = right.criteria;
        if(a ===
            void 0)
            return 1;
        if(b ===
            void 0)
            return -1;
        return a < b ? -1 : a > b ? 1 : 0;
    }), 'value');
};

这个函数对集合中元素, 按照特定的字段或值进行排列,相比Array.prototype.sort方法, sortBy方法支持对对象排序

_.groupBy

_.groupBy = function(obj, val) {
    var result = {};
    // val将被转换为进行分组的处理器函数, 如果val不是一个Function类型的数据, 则将被作为筛选元素时的key值
    var iterator = _.isFunction(val) ? val : function(obj) {
        return obj[val];
    };
    // 迭代集合中的元素
    each(obj, function(value, index) {
        // 将处理器的返回值作为key, 并将相同的key元素放到一个新的数组
        var key = iterator(value, index);
        (result[key] || (result[key] = [])).push(value);
    });
    // 返回已分组的数据
    return result;
};

这个函数将集合中的元素, 按处理器返回的key分为多个数组

_.sortedIndex

_.sortedIndex = function(array, obj, iterator) {
    // 如果没有指定处理器参数, 则使用默认的处理器函数,该函数会返回参数本身
    iterator || ( iterator = _.identity);
    var low = 0, high = array.length;
    // 不断与中间值对比,寻找obj的正确插入点
    while(low < high) {
        // (low + high) >> 1 相当于 Math.floor((low + high) / 2)
        var mid = (low + high) >> 1;
        iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
    }
    // 返回obj插入array之后的索引号
    return low;
};

这个函数的作用是将obj插入已经排序的array中,返回obj在array中的索引号

_.toArray

_.toArray = function(obj) {
    if(!obj)
        return [];
    if(_.isArray(obj))
        return slice.call(obj);
    // 将arguments转换为数组
    if(_.isArguments(obj))
        return slice.call(obj);
    if(obj.toArray && _.isFunction(obj.toArray))
        return obj.toArray();
    // 将对象转换为数组, 数组中包含对象中所有属性的值列表(不包含对象原型链中的属性)
    return _.values(obj);
};

这个函数很简单,作用是将一个集合转换一个数组并返回

_.size

_.size = function(obj) {
    // 如果集合是一个数组, 则计算数组元素数量
    // 如果集合是一个对象, 则计算对象中的属性数量(不包含对象原型链中的属性)
    return _.isArray(obj) ? obj.length : _.keys(obj).length;
};

这个函数用于计算集合中元素的数量,isArray和keys函数后面会介绍到

_.first / _.head / _.take

_.first = _.head = _.take = function(array, n, guard) {
    // 如果没有指定参数n, 则返回第一个元素
    // 如果指定了n, 则返回一个新的数组, 包含顺序指定数量n个元素
    // guard参数用于确定只返回第一个元素, 当guard为true时, 指定数量n无效
    return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};

这个函数用于返回一个数组的第一个或順序指定的n个元素

_.initial

_.initial = function(array, n, guard) {
    // 如果没有传递参数n, 则默认返回除最后一个元素外的其它元素
    // 如果传递参数n, 则返回从最后一个元素开始向前的n个元素外的其它元素
    // guard用于确定只返回一个元素, 当guard为true时, 指定数量n无效
    return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};

这个函数返回一个新数组, 包含除最后一个元素外的其它元素, 或排除从最后一个元素开始向前指定n个元素

_.last

_.last = function(array, n, guard) {
    if((n != null) && !guard) {
        // 计算并指定获取的元素位置n, 直到数组末尾, 作为一个新的数组返回
        return slice.call(array, Math.max(array.length - n, 0));
    } else {
        // 如果没有指定数量, 或guard为true时, 只返回最后一个元素
        return array[array.length - 1];
    }
};

这个函数与first相反,返回数组的最后一个或倒序指定的n个元素

_.rest / _.tail

_.rest = _.tail = function(array, index, guard) {
    // 计算slice的第二个位置参数, 直到数组末尾
    // 如果没有指定index, 或guard值为true, 则返回除第一个元素外的其它元素
    // (index == null)值为true时, 作为参数传递给slice函数将被自动转换为1
    return slice.call(array, (index == null) || guard ? 1 : index);
};

这个函数与initial相反,用于获取除了第一个或指定前n个元素外的其它元素

_.campact

_.compact = function(array) {
    return _.filter(array, function(value) {
        return !!value;
    });
};

这个函数借助filter函数,返回数组中所有值能被转换为true的元素, 返回一个新的数组,不能被转换的值包括 false, 0, ”, null, undefined, NaN, 这些值将被转换为false

_.flatten

_.flatten = function(array, shallow) {
    // 迭代数组中的每一个元素, 并将返回值作为demo传递给下一次迭代
    return _.reduce(array, function(memo, value) {
        // 如果元素依然是一个数组, 进行以下判断:
        // - 如果不进行深层合并, 则使用Array.prototype.concat将当前数组和之前的数据进行连接
        // - 如果支持深层合并, 则迭代调用flatten方法, 直到底层元素不再是数组类型
        if(_.isArray(value))
            return memo.concat( shallow ? value : _.flatten(value));
        // 数据(value)已经处于底层, 不再是数组类型, 则将数据合并到memo中并返回
        memo[memo.length] = value;
        return memo;
    }, []);
};

这个函数用于将一个多维数组合成为一维数组, 支持深层合并,其中第二个参数shallow用于控制合并深度, 当shallow为true时, 只合并第一层, 默认进行深层合并

_.without

_.without = function(array) {
    return _.difference(array, slice.call(arguments, 1));
};

这个函数用于筛选并返回当前数组中与指定数据不相等的差异数据,具体可以参看我后续对difference函数的介绍

.uniq/.unique

_.uniq = _.unique = function(array, isSorted, iterator) {
    // 如果使用了iterator处理器, 则先将当前数组中的数据会先经过按迭代器处理, 并返回一个处理后的新数组
    // 新数组用于作为比较的基准
    var initial = iterator ? _.map(array, iterator) : array;
    // 用于记录处理结果的临时数组
    var results = [];
    // 如果数组中只有2个值, 则不需要使用include方法进行比较, 将isSorted设置为true能提高运行效率
    if(array.length < 3)
        isSorted = true;
    // 使用reduce方法迭代并累加处理结果
    // initial变量是需要进行比较的基准数据, 它可能是原始数组, 也可能是处理器的结果集合(如果设置过iterator)
    _.reduce(initial, function(memo, value, index) {
        // 如果isSorted参数为true, 则直接使用===比较记录中的最后一个数据
        // 如果isSorted参数为false, 则使用include方法与集合中的每一个数据进行对比
        if( isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
            // memo记录了已经比较过的无重复数据
            // 根据iterator参数的状态, memo中记录的数据可能是原始数据, 也可能是处理器处理后的数据
            memo.push(value);
            // 处理结果数组中保存的始终为原始数组中的数据
            results.push(array[index]);
        }
        return memo;
    }, []);
    // 返回处理结果, 它只包含数组中无重复的数据
    return results;
};

这个函数用于对数组中的数据进行去重(使用===进行比较),当isSorted参数不为false时, 将依次对数组中的元素调用include方法, 检查相同元素是否已经被添加到返回值(数组)中,如果调用之前确保数组中数据按顺序排列, 则可以将isSorted设为true, 它将通过与最后一个元素进行对比来排除相同值, 使用isSorted效率会高于默认的include方式,uniq方法默认将以数组中的数据进行对比, 如果声明iterator处理器, 则会根据处理器创建一个对比数组, 比较时以该数组中的数据为准, 但最终返回的唯一数据仍然是原始数组

_.union

_.union = function() {
    // union对参数中的多个数组进行浅层合并为一个数组对象传递给uniq方法进行处理
    return _.uniq(_.flatten(arguments, true));
};

这个函数与uniq作用一致, 不同之处在于union允许在参数中传入多个数组

_.intersection

_.intersection = _.intersect = function(array) {
    // rest变量记录需要进行比较的其它数组对象
    var rest = slice.call(arguments, 1);
    // 使用uniq方法去除当前数组中的重复数据, 避免重复计算
    // 对当前数组的数据通过处理器进行过滤, 并返回符合条件(比较相同元素)的数据
    return _.filter(_.uniq(array), function(item) {
        // 使用every方法验证每一个数组中都包含了需要对比的数据
        // 如果所有数组中均包含对比数据, 则全部返回true, 如果任意一个数组没有包含该元素, 则返回false
        return _.every(rest, function(other) {
            // other参数存储了每一个需要进行对比的数组
            // item存储了当前数组中需要进行对比的数据
            // 使用indexOf方法搜索数组中是否存在该元素(可参考indexOf方法注释)
            return _.indexOf(other, item) >= 0;
        });
    });
};

这个函数用于获取当前数组与其它一个或多个数组的交集元素,从第二个参数开始为需要进行比较的一个或多个数组

_.difference

_.difference = function(array) {
    // 对第2个参数开始的所有参数, 作为一个数组进行合并(仅合并第一层, 而并非深层合并)
    // rest变量存储验证数据, 在本方法中用于与原数据对比
    var rest = _.flatten(slice.call(arguments, 1), true);
    // 对合并后的数组数据进行过滤, 过滤条件是当前数组中不包含参数指定的验证数据的内容
    // 将符合过滤条件的数据组合为一个新的数组并返回
    return _.filter(array, function(value) {
        return !_.include(rest, value);
    });
};

这个函数会筛选并返回当前数组中与指定数据不相等的差异数据,一般用于删除数组中指定的数据, 并得到删除后的新数组

小结

今天一共介绍了21个函数的具体实现,我都写累了,大家可能也看累了吧,我觉得写太多也不利于大家消化这些知识,今天就到这儿吧。thx for reading, hope u enjoy


发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>