Underscore.js 源码分析(第二出)

前言

这里是 underscore.js 源码分析的第二出,集合相关函数,包含一些遍历,对象的简单处理。

// 循环遍历
_.each = _.forEach = function (obj, iteratee, context) {
    // 通过 optimizeCb() 方法返回一个回调函数
    iteratee = optimizeCb(iteratee, context);
    var i, length;
    // 如果是数组(包括类数组)
    if (isArrayLike(obj)) {
        //  遍历数组的每一个元素,并把此值及下标和当前的遍历对象作为 iteratee 回调的参数
        for (i = 0, length = obj.length; i < length; i++) {
            iteratee(obj[i], i, obj);
        }
    } else {
        // 如果是一个对象。取所所有 key 值(一个key值组成的数组)
        var keys = _.keys(obj);
        for (i = 0, length = keys.length; i < length; i++) {
            //  遍历对象的每一个 key,并把值和键及当前的遍历对象作为 iteratee 回调的参数
            iteratee(obj[keys[i]], keys[i], obj);
        }
    }
    return obj;
};

_.map = _.collect = function (obj, iteratee, context) {
    // 调用 cb() 方法返回 optimizeCb(value, context, argCount) 执行结果
    iteratee = cb(iteratee, context);
    // 如果不是数组,则返回对象的键组成的数组
    var keys = !isArrayLike(obj) && _.keys(obj),
        // keys 或者数组的长度
        length = (keys || obj).length,
        // 创建一个长度为 length 的数组
        results = Array(length);
    for (var index = 0; index < length; index++) {
        // 根据 index 取出 key 或者数组下标,
        var currentKey = keys ? keys[index] : index;
        // 调用回调 iteratee(),前把它的返回结果对应保存到 results 数组中
        results[index] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
};

// 创建从左到右的累加器
_.reduce = _.foldl = _.inject = createReduce(1);

// 创建从右到左的累加器
_.reduceRight = _.foldr = createReduce(-1);

// 获取指定 key 的值
_.find = _.detect = function (obj, predicate, context) {
    // 如果是一个数组,返回 _.findIndex 方法(返回数组下标的方法),否则返回 _.findKey 方法(返回对象键的方法)
    var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
    var key = keyFinder(obj, predicate, context);
    // 如果 key 不等于 undefined 或者不等于 -1,返回对象 key 所对应的值
    if (key !== void 0 && key !== -1) return obj[key];
};

// 过滤出通过 predicate 检测的元素集合
_.filter = _.select = function (obj, predicate, context) {  // function(){return}
    var results = [];
    // 执行 cb() 方法,返回 optimizeCb(value, context, argCount) 的执行
    predicate = cb(predicate, context);
    _.each(obj, function (value, index, list) {
        // 调用回调,其它返回值为 true 或者是 false,如果为 true 则把当前值放到结果数组中
        if (predicate(value, index, list)) results.push(value);
    });
    return results;
};

// 过滤出没能通过 predicate 检测的元素集合
_.reject = function (obj, predicate, context) {
    // _.negate() 修改传入函数的 this 指向 window,并返回一个匿名函数
    // 调用 cb() 返回 optimizeCb(value, context, argCount) 的执行结果
    return _.filter(obj, _.negate(cb(predicate)), context);
};

// 所有元素通过 predicate 检测,则返回 true
_.every = _.all = function (obj, predicate, context) {
    predicate = cb(predicate, context);
    // 取 key 集合
    var keys = !isArrayLike(obj) && _.keys(obj),
        // 获取对象的长度
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
        var currentKey = keys ? keys[index] : index;
        // 只要有一个元素没通过 predicate 检测,直接返回 false
        if (!predicate(obj[currentKey], currentKey, obj)) return false;
    }
    // 跑到这里说明全部元素通过了 predicate 检测
    return true;
};

// 这个方法跟 _.every 实现差不多,只要有一个通过 predicate 检测的就返回 true
_.some = _.any = function (obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
        var currentKey = keys ? keys[index] : index;
        if (predicate(obj[currentKey], currentKey, obj)) return true;
    }
    return false;
};

// 判断对象是否包含某个值
_.contains = _.includes = _.include = function (obj, item, fromIndex, guard) {
    // 如果不是数组 则取出对象的所有值组成一个数组
    if (!isArrayLike(obj)) obj = _.values(obj);
    // 如果没有设置开始下标,则从零开始
    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
    // 如果找到返回 true
    return _.indexOf(obj, item, fromIndex) >= 0;
};

// 对 obj 的每一个元素都执行一遍 path 方法,不过这个 path 有可能是一个数组,此时就需要用到 deepGet() 内部方法进行处理
_.invoke = restArguments(function (obj, path, args) {
    var contextPath, func;
    // 如果 path 是一个函数
    if (_.isFunction(path)) {
        func = path;
    } else if (_.isArray(path)) { // 保存一份下标从零开始到倒数第二个的数组副本
        contextPath = path.slice(0, -1);
        path = path[path.length - 1]; // 取出最后一个
    }
    return _.map(obj, function (context) {
        var method = func;
        if (!method) { // 如果 method 不为真
            if (contextPath && contextPath.length) {
                // 深度获取指定 key 的值
                context = deepGet(context, contextPath);
            }
            // 如果context 为 undefined 或者 null,则返回 undefined
            if (context == null) return void 0;
            // 获取 context 中的 path 属性的值
            method = context[path];
        }
        // 如果是 undefined 或者是 null 放入返回 method 的值,否则传入 args 参数并调用 method 方法
        return method == null ? method : method.apply(context, args);
    });
});

// 获取 obj 对象第一项中指定 key 的值所组成的数组,可以看作是 _.map() 方法的常用方式的提取
_.pluck = function (obj, key) {
// 返回 _.map() 方法处理后的结果
    return _.map(obj, _.property(key));
};

// 返回包含指定键值对的项的数组
_.where = function(obj, attrs) {
    return _.filter(obj, _.matcher(attrs));
};

// 返回包含指定键值对的项
_.findWhere = function(obj, attrs) {
    return _.find(obj, _.matcher(attrs));
};