资讯专栏INFORMATION COLUMN

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

yuxue / 1308人阅读

摘要:第四个判断如果是对象执行返回一个断言函数,用来判定传入对象是否匹配指定键值属性。都不匹配最后执行,返回传入的对象的属性。设置的值并生成函数,等同于,使具有属性且有值则返回,否则返回,这是一个判断函数。

在第二小章节里面我按照源码顺序介绍几个方法,源码紧接着第一章继续:

  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 类型也蛮有意思,有兴趣的同学可以参考 Infinity、POSITIVE_INFINITY 和 NEGATIVE_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 时,同学们应该很容易理解 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 函数,没什么好说的,参考 Map、Map.prototype、WeakMap 用于知识储备,至于作者的 _.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).aspx 和 array-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 计算顺序相反即从右面向左面开始。

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

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

相关文章

  • 如何自制 JS 注释文档生成工具

    摘要:组件的选择命令行工具首先我们需要一个命令行工具来方便的执行命令,这里我们选择组件,如果不喜欢使用且有能力的人完全可以通过组件自己封装执行命令函数。 对于一个成熟的项目而言,一定需要一个注释文档生成工具,我们有很多可选的开源项目,如jsdoc、yuidocjs 等等,拥有这些强大的工具我们完全可以胜任任何注释方面的管理了么? 一个成熟的开发者都会知道不管怎么样的项目都会在不同的开发条件下...

    Cristalven 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第一章

    摘要:新出台的则规定,包括六种原始类型和,还有一种,详见数据类型和数据结构。用于返回一个由给定对象的所有可枚举自身属性的属性名组成的数组,。接下来判断数字进行相应的操作,其中有和两个方法,详见和。 一直想写一篇这样的文章,于是心动不如行动,这里选择的是 Underscore.js 1.8.3 版本,源码注释加在一起1625行。 Underscore.js 1.8.3 http://unde...

    MockingBird 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第六章

    摘要:用来构成和两个函数,主要针对的是为了将函数调用模式更改为构造器调用和方法调用。通过函数设定时间为毫秒后执行函数的回调函数,用以达到在规定时间毫秒时执行函数的目的,并且规定时间内只执行一次函数。 北京的雨已经断断续续下了好久,昏昏欲睡的躲在家里不愿意出门,火影忍者快要结束了,一拳超人第二季据说还要等好多年,勇者大冒险貌似断更了,我又是在不喜欢海贼王的画风,所以,我该看什么好呢。 va...

    v1 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第四章

    摘要:接收三个参数分别为回调和,其中与是可选参数。官网释义排序一个列表组成一个组,并且返回各组中的对象的数量的计数。类似,但是不是返回列表的值,而是返回在该组中值的数目。 继续前面的内容,前文我们提到了很多方法的讲解,其实到这里就已经差不多了,因为大部分代码其实都是套路,一些基础函数再灵活变化就可以组成很多实用的功能。 _.sortBy = function(obj, iteratee,...

    zhaochunqi 评论0 收藏0
  • 1625解开 underscore.js 面纱 - 第三章

    摘要:传入值进行判断以此决定函数,将三个参数包括回调传入中其中回调函数充当迭代器进行真值检测,最后。是从一个中随机返回值,并且返回值受限于这个参数,如果没有传入或者传入了则执行语句,目的是将判断处理之后返回单一值。 今天继续上次的内容,之前我们讲到了 reduce 的用法,其实我觉得用法倒是其次的关键是作者实现 reduce 过程中所灵活用到的函数处理方法,我们只要有心稍加总觉完全可以拿来主...

    dack 评论0 收藏0

发表评论

0条评论

yuxue

|高级讲师

TA的文章

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