underscore.js 170 -- 293 行

来源:互联网 发布:八爪鱼数据采集能干嘛 编辑:程序博客网 时间:2024/06/03 15:23
  // The cornerstone, an `each` implementation, aka `forEach`.  // Handles raw objects in addition to array-likes. Treats all  // sparse array-likes as if they were dense.  _.each = _.forEach = function(obj, iteratee, context) {    iteratee = optimizeCb(iteratee, context);    var i, length;    if (isArrayLike(obj)) {      for (i = 0, length = obj.length; i < length; i++) {        iteratee(obj[i], i, obj);      }    } else {      var keys = _.keys(obj);      for (i = 0, length = keys.length; i < length; i++) {        iteratee(obj[keys[i]], keys[i], obj);      }    }    return obj; // If it's totally an object  }; //说明item不影响数组或者伪数组本身 // Return the results of applying the iteratee to each element.  _.map = _.collect = function(obj, iteratee, context) {    iteratee = cb(iteratee, context);    var keys = !isArrayLike(obj) && _.keys(obj),        length = (keys || obj).length,        results = Array(length);    for (var index = 0; index < length; index++) {      var currentKey = keys ? keys[index] : index;      results[index] = iteratee(obj[currentKey], currentKey, obj);    }    return results;  };  // Create a reducing function iterating left or right.  var createReduce = function(dir) {    // Wrap code that reassigns argument variables in a separate function than    // the one that accesses `arguments.length` to avoid a perf hit. (#1991)    var reducer = function(obj, iteratee, memo, initial) {      var keys = !isArrayLike(obj) && _.keys(obj),          length = (keys || obj).length,          index = dir > 0 ? 0 : length - 1;      if (!initial) {        memo = obj[keys ? keys[index] : index];        index += dir;      }      for (; index >= 0 && index < length; index += dir) {        var currentKey = keys ? keys[index] : index;        memo = iteratee(memo, obj[currentKey], currentKey, obj);      }      return memo;    };    return function(obj, iteratee, memo, context) {      var initial = arguments.length >= 3;      return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);    };  };  // **Reduce** builds up a single result from a list of values, aka `inject`,  // or `foldl`.  _.reduce = _.foldl = _.inject = createReduce(1);  // The right-associative version of reduce, also known as `foldr`.  _.reduceRight = _.foldr = createReduce(-1);  // Return the first value which passes a truth test. Aliased as `detect`.  _.find = _.detect = function(obj, predicate, context) {    var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;    var key = keyFinder(obj, predicate, context);    if (key !== void 0 && key !== -1) return obj[key];  };  // Return all the elements that pass a truth test.  // Aliased as `select`.  _.filter = _.select = function(obj, predicate, context) {    var results = [];    predicate = cb(predicate, context);    _.each(obj, function(value, index, list) {      if (predicate(value, index, list)) results.push(value);    });    return results;  };  // Return all the elements for which a truth test fails.  _.reject = function(obj, predicate, context) {    return _.filter(obj, _.negate(cb(predicate)), context);  };  // Determine whether all of the elements match a truth test.  // Aliased as `all`.  _.every = _.all = 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 false;    }    return true;  };  // Determine if at least one element in the object matches a truth test.  // Aliased as `any`.  _.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;  };  // Determine if the array or object contains a given item (using `===`).  // Aliased as `includes` and `include`.  _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {    if (!isArrayLike(obj)) obj = _.values(obj);    if (typeof fromIndex != 'number' || guard) fromIndex = 0;    return _.indexOf(obj, item, fromIndex) >= 0;  };  // Invoke a method (with arguments) on every item in a collection.  _.invoke = restArgs(function(obj, path, args) {    var contextPath, func;    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) {        if (contextPath && contextPath.length) {          context = deepGet(context, contextPath);        }        if (context == null) return void 0;        method = context[path];      }      return method == null ? method : method.apply(context, args);    });  });

终于到了函数各个功能实现的部分了,先回顾一下之前我们看到的准备函数:
1. getLength (获取所有跟length有关的属性)
2. isArrayLike (该对象是否跟数组类似,拥有长度属性,并且在0到最大数组长之内)
3. baseCreate (继承原型所用)
4. cb (用以创造一个新的迭代函数,根据不同的情况进行处理,如果第一个传入参数是函数,则等同与optimizeCb处理)
5. optmizeCb (优化函数,绑定函数this,并且规定传入参数的数量)

_.each

_.each = _.forEach = function(obj, iteratee, context) {    iteratee = optimizeCb(iteratee, context);    var i, length;    if (isArrayLike(obj)) {      for (i = 0, length = obj.length; i < length; i++) {        iteratee(obj[i], i, obj);      }    } else {      var keys = _.keys(obj);      for (i = 0, length = keys.length; i < length; i++) {        iteratee(obj[keys[i]], keys[i], obj);      }    }    return obj;   }; 

_.each没什么好说的,就是在迭代器中传入对象每一个值,
不同情况不同的处理方式,伪数组或者数组则处理1,2,3,4的部分,如果是一个对象,则遍历keys。

_.map

 _.map = _.collect = function(obj, iteratee, context) {    iteratee = cb(iteratee, context);    var keys = !isArrayLike(obj) && _.keys(obj),        length = (keys || obj).length,        results = Array(length);    for (var index = 0; index < length; index++) {      var currentKey = keys ? keys[index] : index;      results[index] = iteratee(obj[currentKey], currentKey, obj);    }    return results;  };

相当于在内部先创造一个空数组,然后在当中分别对应迭代器返回的值,迭代器传入参数分别为:每个值,index,对象本身,然后返回这个空数组

从代码中可以看出,_.each用作纯遍历工具比该方法更加高效。

_.reduce

 // Create a reducing function iterating left or right.  var createReduce = function(dir) {    // Wrap code that reassigns argument variables in a separate function than    // the one that accesses `arguments.length` to avoid a perf hit. (#1991)    var reducer = function(obj, iteratee, memo, initial) {      var keys = !isArrayLike(obj) && _.keys(obj),          length = (keys || obj).length,          index = dir > 0 ? 0 : length - 1;      if (!initial) {        memo = obj[keys ? keys[index] : index];        index += dir;      }      for (; index >= 0 && index < length; index += dir) {        var currentKey = keys ? keys[index] : index;        memo = iteratee(memo, obj[currentKey], currentKey, obj);      }      return memo;    };    return function(obj, iteratee, memo, context) {      var initial = arguments.length >= 3;      return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);    };  };  // **Reduce** builds up a single result from a list of values, aka `inject`,  // or `foldl`.  _.reduce = _.foldl = _.inject = createReduce(1);  // The right-associative version of reduce, also known as `foldr`.  _.reduceRight = _.foldr = createReduce(-1);

我们知道reduce的就是

_.reduce(obj, function () {}, accumulator, context)

其中这个

 var createReduce = function(dir) {    // Wrap code that reassigns argument variables in a separate function than    // the one that accesses `arguments.length` to avoid a perf hit. (#1991)    var reducer = function(obj, iteratee, memo, initial) {      var keys = !isArrayLike(obj) && _.keys(obj),          length = (keys || obj).length,          index = dir > 0 ? 0 : length - 1;      if (!initial) {        memo = obj[keys ? keys[index] : index];        index += dir;      }      for (; index >= 0 && index < length; index += dir) {        var currentKey = keys ? keys[index] : index;        memo = iteratee(memo, obj[currentKey], currentKey, obj);      }      return memo;    };    return function(obj, iteratee, memo, context) {      var initial = arguments.length >= 3;      return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);    };  };

即为构造器,构造reduce起点或者终点,dir参数正负决定方向,大小决定遍历的方式

memo代表之前存放的数值,如未规定,则默认为开头
迭代器中的参数大概为:memo, currentItem, currentIndex, obj
然后再return一个新的memo出来

完全可以把这个函数简化点:

var createReduce = function (dir) {    function reduce (obj, iteratee, memo, initial) {        var index = 0        var length = obj.length        if (!initial) {            memo = obj[index]            index+= dir        }        for (; index >=0 && index < length; index += dir) {            var currentItem = obj[index]            memo = iteratee(memo, currentItem, index, obj)        }        return memo    }    return function (obj, iteratee, memo) {        var initial = arguments.length >= 3        return reduce(obj, iteratee, memo, initial)    }}

其中initial = arguments.length >= 3用于自动处理当规定memo后,reduce自动将其initial为true

那么reduce就方便了:

_.reduce = _.foldl = _.inject = createReduce(1); _.reduceRight = _.foldr = createReduce(-1);

_.find

 _.find = _.detect = function(obj, predicate, context) {    var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;    var key = keyFinder(obj, predicate, context);    if (key !== void 0 && key !== -1) return obj[key];  };

看到这个函数。 第一眼就发现一个.findIndex和.findKey
估计是在后面出现的,于是搜索一下找到这两个函数:

_.findIndex = createPredicateIndexFinder(1); _.findKey = function(obj, predicate, context) {    predicate = cb(predicate, context);    var keys = _.keys(obj), key;    for (var i = 0, length = keys.length; i < length; i++) {      key = keys[i];      if (predicate(obj[key], key, obj)) return key;    }  };var createPredicateIndexFinder = function(dir) {    return function(array, predicate, context) {      predicate = cb(predicate, context);      var length = getLength(array);      var index = dir > 0 ? 0 : length - 1;      for (; index >= 0 && index < length; index += dir) {        if (predicate(array[index], index, array)) return index;      }      return -1;    };  };

我们先看findKey:

_.findKey = function(obj, predicate, context) {    predicate = cb(predicate, context);    var keys = _.keys(obj), key;    for (var i = 0, length = keys.length; i < length; i++) {      key = keys[i];      if (predicate(obj[key], key, obj)) return key;    }  };

我们可以发现当中的predicate是个用来作判断的函数,如果其返回值为true, 则直接返回第一个符合条件的索引,其中向predicate传入的参数为item, key, obj

再看_.findIndex:

_.findIndex = createPredicateIndexFinder(1);var createPredicateIndexFinder = function(dir) {    return function(array, predicate, context) {      predicate = cb(predicate, context);      var length = getLength(array);      var index = dir > 0 ? 0 : length - 1;      for (; index >= 0 && index < length; index += dir) {        if (predicate(array[index], index, array)) return index;      }      return -1;    };  };

没得说,跟我们刚才那个createReduce的dir作用一样,正负决定方向,dir参数大小决定方式,同样return一个新的function,找到符合条件的立即返回,若找不到则返回-1

回到_.find:

_.find = _.detect = function(obj, predicate, context) {    var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;    var key = keyFinder(obj, predicate, context);    if (key !== void 0 && key !== -1) return obj[key];  };

我们发现这是对对象和数组类型两种不同情况更高一层的封装,最底层的方法当然是createPredicateIndexFinder与_.findKey这个可能较为重要,以后可能会继续用到。

_.filter

 _.filter = _.select = function(obj, predicate, context) {    var results = [];    predicate = cb(predicate, context);    _.each(obj, function(value, index, list) {      if (predicate(value, index, list)) results.push(value);    });    return results;  };

果不其然,之前类似的方法我们又得用到一次,只不过这回内部先造出了一个新的数组,然后用.each遍历我们的obj,然后用函数判断是否符合要求,是则将其推入数组,最终返回一个数组,看来.each是最高效的一个遍历方法,也更底层一些。

_.reject

// Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) {    return _.filter(obj, _.negate(cb(predicate)), context);  };

这个么,

_.negate = function(predicate) {    return function() {      return !predicate.apply(this, arguments);    };  };

说白了就是将predicate的判断结果反过来

同理reject就是说将所有满足条件的值全部弹出,保留下不满足条件的值。

_.every

 _.every = _.all = 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 false;    }    return true;  };

从内部的for循环我们可以看出,一旦obj内发现任何不满足条件的值,就返回false,如果obj内的所有值遵纪守法最终就返回true

_.some

  // Determine if at least one element in the object matches a truth test.  // Aliased as `any`.  _.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;  };

我们发现,这个函数与.every/.all最大的区别就是。。。。return的值反了过来,也就是说,一旦有任何函数满足条件,就返回true,都不满足最终会返回false

感觉这个用_.any比较合适,因为some实在是不能立即判断出这个函数的作用。

_.contains

// Determine if the array or object contains a given item (using `===`).  // Aliased as `includes` and `include`.  _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {    if (!isArrayLike(obj)) obj = _.values(obj);    if (typeof fromIndex != 'number' || guard) fromIndex = 0;    return _.indexOf(obj, item, fromIndex) >= 0;  };

真是要吐了,怎么又出现了奇怪的函数。。。能在这好好写不行吗?
这里写图片描述
没办法,开搜

  _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);  // _.findIndex我们已经知道就是返回第一个满足条件的值的index_.sortedIndex = function(array, obj, iteratee, context) {    iteratee = cb(iteratee, context, 1);    var value = iteratee(obj);    var low = 0, high = getLength(array);    while (low < high) {      var mid = Math.floor((low + high) / 2);      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;    }    return low;  }; //二分搜索,输入的值需要为升序,返回的值为第一个比条件数字大的值的索引 ```

然后就是createIndexFinder:
该方法参考网址

// Generator function to create the indexOf and lastIndexOf functions  // _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);  // _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);  function createIndexFinder(dir, predicateFind, sortedIndex) {    // API 调用形式    // _.indexOf(array, value, [isSorted])     // _.indexOf(array, value, [fromIndex])     // _.lastIndexOf(array, value, [fromIndex])     return function(array, item, idx) {      var i = 0, length = getLength(array);      // 如果 idx 为 Number 类型      // 则规定查找位置的起始点      // 那么第三个参数不是 [isSorted]      // 所以不能用二分查找优化了      // 只能遍历查找      if (typeof idx == 'number') {        if (dir > 0) { // 正向查找          // 重置查找的起始位置          i = idx >= 0 ? idx : Math.max(idx + length, i);        } else { // 反向查找          // 如果是反向查找,重置 length 属性值          length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;        }      } else if (sortedIndex && idx && length) {        // 能用二分查找加速的条件        // 有序 & idx !== 0 && length !== 0        // 用 _.sortIndex 找到有序数组中 item 正好插入的位置        idx = sortedIndex(array, item);        // 如果正好插入的位置的值和 item 刚好相等        // 说明该位置就是 item 第一次出现的位置        // 返回下标        // 否则即是没找到,返回 -1        return array[idx] === item ? idx : -1;      }      // 特判,如果要查找的元素是 NaN 类型      // 如果 item !== item      // 那么 item => NaN      if (item !== item) {        idx = predicateFind(slice.call(array, i, length), _.isNaN);        return idx >= 0 ? idx + i : -1;      }      // O(n) 遍历数组      // 寻找和 item 相同的元素      // 特判排除了 item 为 NaN 的情况      // 可以放心地用 `===` 来判断是否相等了      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {        if (array[idx] === item) return idx;      }      return -1;    };  }

回到_.contains

 _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {    if (!isArrayLike(obj)) obj = _.values(obj);    if (typeof fromIndex != 'number' || guard) fromIndex = 0;    return _.indexOf(obj, item, fromIndex) >= 0;  };

这中间guard的参数意义也就变得明了,即保证传入的obj是升序的,则此时会用二分法查找,效率会变得更高。
总结:createPredicateIndex –> .indexof –> .contains
一步步封装,第一层是构造一个寻找函数,第二层则是对应不同情况进行加强修补调用,第三层则更是简化传入参数步骤。

原创粉丝点击