资讯专栏INFORMATION COLUMN

Underscore源码解析(三)

Prasanta / 1310人阅读

摘要:本文同步自我得博客前两天在微博上看到的微博推荐了我的前两篇文章,有点意外和惊喜。没看过前两篇博客的朋友可以戳这里源码解析一源码解析二上一篇文章介绍了的个函数的具体实现细节,今天将继续介绍其他的函数。

本文同步自我得博客:http://www.joeray61.com

前两天在微博上看到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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/78154.html

相关文章

  • Underscore源码解析(四)

    摘要:本文同步自我得博客我在这个系列的第一篇文章说过,我学是为了在学的时候少一些阻碍,从第一篇的写作时间到今天,大概也有个十几二十天,感觉拖得有点久,所以今天将会是源码解析系列的最后一篇文章,我会在这篇文章中介绍剩下的所有函数。 本文同步自我得博客:http://www.joeray61.com 我在这个系列的第一篇文章说过,我学underscore是为了在学backbone的时候少一些阻碍...

    高胜山 评论0 收藏0
  • Underscore源码解析(一)

    摘要:本文同步自我得博客最近准备折腾一下,在事先了解了之后,我知道了对这个库有着强依赖,正好之前也没使用过,于是我就想先把彻底了解一下,这样之后折腾的时候也少一点阻碍。 本文同步自我得博客:http://www.joeray61.com 最近准备折腾一下backbone.js,在事先了解了backbone之后,我知道了backbone对underscore这个库有着强依赖,正好undersc...

    neu 评论0 收藏0
  • Underscore源码解析(二)

    摘要:本文同步自我得博客最近十几天都在忙毕业论文的事,所以上一次为大家介绍完这个框架的结构或者说是这个框架的设计思路之后就一直没动静了,今天我又满血复活了,让我们继续来探索的源码奥秘吧。 本文同步自我得博客:http://www.joeray61.com 最近十几天都在忙毕业论文的事,所以上一次为大家介绍完underscore这个框架的结构(或者说是这个框架的设计思路)之后就一直没动静了,今...

    骞讳护 评论0 收藏0
  • JS基础篇-underscore源码解析

    摘要:总想找个机会夯实一下自己的基础,正好最近略有清闲,看视频读书撸代码我选择了第三者怎么感觉有点别扭,看视频的话效率不高适合入门,看书的话一本你不知道的推荐给大家,选择继续看书的话还是算了吧,毕竟读万卷书不如行万里路是吧。 总想找个机会夯实一下自己的JS基础,正好最近略有清闲,看视频?读书?撸代码?我选择了第三者(怎么感觉有点别扭),看视频的话效率不高适合入门,看书的话,一本《你不知道的J...

    anyway 评论0 收藏0
  • 也谈面试必备问题之 JavaScript 数组去重

    摘要:而数组元素去重是基于运算符的。而如果有迭代函数,则计算传入迭代函数后的值,对值去重,调用方法,而该方法的核心就是调用方法,和我们上面说的方法一异曲同工。 Why underscore (觉得这部分眼熟的可以直接跳到下一段了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中。 阅读一些著名框架类库的源码,就好像...

    Coly 评论0 收藏0

发表评论

0条评论

Prasanta

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<