1625行,解开 underscore.js 的面纱 - 第二章

在第二小章节内里我依据源码递次引见几个要领,源码紧接着第一章继承:

  var builtinIteratee;

builtinIteratee,内置的 Iteratee (迭代器)。

  var cb = function(value, context, argCount) {
    if (_.iteratee !== builtinIteratee) return _.iteratee(value, context);
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value)) return _.matcher(value);
    return _.property(value);
  };

cb 函数接收三个参数,连续四个推断,第一个推断 _.iteratee,依据 JAVASCRIPT 的上下文,起首 builtinIteratee 为 undefined,然 cb 函数内 builtinIteratee 为 undefined,接下来就是 _.iteratee = builtinIteratee 内里的 cb 函数,so…接着第二个推断传入参数是不是为空值,假如是则返回 _.identity 函数,即当前传入值。第三个推断传入值是要领则实行 optimizeCb 函数。第四个推断假如是对象实行返回一个断言函数,用来剖断传入对象是不是婚配attrs指定键/值属性。都不婚配末了实行 _.property,返回传入的对象的 key 属性。

  _.iteratee = builtinIteratee = function(value, context) {
    return cb(value, context, Infinity);
  };

_.iteratee 这个函数平常认为是一个迭代器,这里是作者的主观写法,由于从意义上讲, cb 函数和 _.iteratee 函数很类似,以至说只需略加修改 cb 完全能够替换掉 _.iteratee,作者用 _.iteratee 包装 cb 并供应外部接见,虽然现实工作中我们应用 _.iteratee 函数并不罕见,但假如用的好相对是一利器,由 underscore.js 源码内部随处可见的 cb(),就晓得这一函数的作用之大。在 underscorereturn cb() 传入了第三个参数 Infinity,意为参数范例为 Infinity 当实行第三个 cb 函数的 if 推断,实行 return optimizeCb(); 时就会发挥其作用,Infinity 范例也蛮有意思,有兴致的同砚能够参考 InfinityPOSITIVE_INFINITYNEGATIVE_INFINITY

  var restArgs = function(func, startIndex) {
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function() {
      var length = Math.max(arguments.length - startIndex, 0);
      var rest = Array(length);
      for (var index = 0; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      switch (startIndex) {
        case 0: return func.call(this, rest);
        case 1: return func.call(this, arguments[0], rest);
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };

restArgs(其他的参数),什么意思呢,我们看它传入了一个 function 和 一个 Number 范例的 startIndex 标识,起首处置惩罚的是 startIndex。三元运算推断 startIndex 是不是存在,是则为 +startIndex,不然为 func.length - 1 即传入 function 中的传入形参的数目减一,举个例子如:

  var aFunction = function(a,b,c){};
  function(a){
      console.log(a.length)    //3
  }

这么做的目标是什么呢,我们都晓得在一个 Array 中数组排序是从 0 最先,所以就不难明白 func.length - 1,然则 +startIndex 又是为何呢,答案是同样是斟酌数组排序是从 0 最先。实在在源码中 restArgs 这个内部函数作者还并没有效到过 startIndex 这个参数,假如须要运用那末它的意义在于 return function 的时刻处置惩罚 function 中的一部分参数,我们如今假定运用了 startIndex 参数,假如 startIndex >2 即抛去 arguments[startIndex + 1] 作为传入参数的一步限制,然后将 arguments[arguments.length - startIndex + 1] ~ arguments[arguments.length] 封装数组作为 arguments[startIndex] 传入,固然这历程当中须要将 arguments[arguments.length - startIndex + 1] ~ arguments[arguments.length] 从 arguments 删除,所以源码中应用了多个 Array 用于这一历程其目标就是重组 arguments。而当 0<startIndex<2 时,同砚们应当很轻易明白 switch (startIndex),这里就不再多说了。
前面说到作者并没有运用 startIndex 这个参数,那末没有 startIndex 是什么状况呢,startIndex = func.length - 1 就是说设定 Array 的长度即 arguments 的长度,我们能够看到作者对 restArgs 这个函数很注重,而且彷佛一向在优化它,作者想要做什么也不得而知,毕竟抛开 startIndex 的话:

  var restArgs = function(func) {
    startIndex = func.length - 1;
    return function() {
      var rest = Array(1);
      rest[0] = arguments[startIndex];
      var args = Array(arguments.length);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };

等同于:

  var restArgs = function(func) {
    return function() {
      return func.apply(this, arguments);
    };
  };

作者将5行代码扩展到21行,实在就是为了一个 startIndex 罢了。

  var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    if (nativeCreate) return nativeCreate(prototype);
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };

baseCreate 用于建立一个清洁且只存在具有想要其具有 prototype 的函数,第一个推断是不是具有 prototype 参数,第二个推断应用 Object.create 建立,余下则是本身应用 Ctor 这个空函数建立,没什么可细说的。

  var property = function(key) {
    return function(obj) {
      return obj == null ? void 0 : obj[key];
    };
  };

property 用于猎取 obj 的 key 值,经由过程 property() 设置 key ,重点是设置两个字,有 key 则以没有则建立之。

  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;

设置 一个最大值 MAX_ARRAY_INDEX,Math.pow(2, 53) - 1 意为2的53次幂即是9007199254740991,Math 的相干函数参考 Math,实在我一向认为 MAX_ARRAY_INDEX 并不必设置这么大的值,Math.pow(2, 16) 就足以。

  var getLength = property('length');

设置 obj 的 key 值并天生函数,等同于:

  var getLength = function(obj) {
         return obj == null ? void 0 : obj['length'];
    };
  var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };

isArrayLike,使 Obj 具有 length 属性且有值则返回 true,不然返回 false,这是一个推断函数。

  _.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;
  };

我一向认为 JAVASCRIPT 最英华的就是回调的实行体式格局,虽然互联网上一些文章总在说回调毁了统统,随声附和等等,然则回调支持起了一切的框架,而且回调很文雅用的好能够很惬意,回调不是毁了统统只是由于某些人不适当的设置回调毁了他本身的代码。在 _.forEach 中 iteratee 即回调函数,个中应用了 optimizeCb 优化回调,然后是一个通例推断,这里为何用 isArrayLike(obj) 而不是 isArray(obj) 来推断是不是是数组呢,留下一个思考问题。

  _.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;
  };

封装 map 函数,没什么好说的,参考 MapMap.prototypeWeakMap 用于学问贮备,至于作者的 _.map 更多的是依据一定的前提遍历 obj 中的元素,与 _.forEach 的更大区别是 _.forEach 不会对传入的 obj 做修改直接 return obj,而 _.mapreturn resultsreturn results 是每一个 iteratee 回调的鸠合。

  var createReduce = function(dir) {
    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);
    };
  };

createReduce,建立 reduce。关于 reduce 的引见可见 reduce 要领 (Array) (JavaScript):https://msdn.microsoft.com/library/ff679975(v=vs.94).aspxarray-reduce,作者这里的 reduce 一定不是如许,但既然命名为 createReduce,想来也脱不了太多关联。函数中 reducer 起首定义 keys,其值为 obj 的 key 鸠合或许 false,背面几个语句里都有关于 keys 的三元运算,目标就是消除 obj 不为 Object 的可能性。接下来推断传入 initial,假如传入 initial 为 false 则默许 memo 值为 keys[keys.length-1] || 0,以后是 for 轮回遍历回调,并返回末了一个回调值。跳出 reducer 函数 return function 的恰恰是援用 reducer 函数的外部接口,因而一切统统都连接上了,包含 initial 的定义是 arguments 长度大于即是3等等。
我们再从新过一遍代码,在最外部 return 的时刻推断 initial,现实上就是再肯定是不是传入了 memo 和 context,固然最主要的就是 memo,以此来肯定在内部 reducer 的时刻是不是具有初始值。在这里我认为作者应当对 memo 举行范例推断的,假如是 Number 或许 String 还说的过去,然则假如传入 memo 是 Object 就有点说不过去了,会失足的。比方:

    _.reduce([1, 2, 3], function(memo, num){ return memo + num; });
    6
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 1);
    7
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, '1');
    "1123"
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, []);
    "123"
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, [1,2]);
    "1,2123"
    _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, {a:1});
    "[object Object]123"
  _.reduce = _.foldl = _.inject = createReduce(1);

这里就是用 createReduce 包装好的 _.reduce,不诠释。

  _.reduceRight = _.foldr = createReduce(-1);

这里就是用 createReduce 包装好的 _.reduceRight,与 _.reduce 盘算递次相反即从右面向左面最先。

    原文作者:kangkk
    原文地址: https://segmentfault.com/a/1190000005647883
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞