资讯专栏INFORMATION COLUMN

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

dack / 770人阅读

摘要:传入值进行判断以此决定函数,将三个参数包括回调传入中其中回调函数充当迭代器进行真值检测,最后。是从一个中随机返回值,并且返回值受限于这个参数,如果没有传入或者传入了则执行语句,目的是将判断处理之后返回单一值。

今天继续上次的内容,之前我们讲到了 reduce 的用法,其实我觉得用法倒是其次的关键是作者实现 reduce 过程中所灵活用到的函数处理方法,我们只要有心稍加总觉完全可以拿来主义,丰富自己的代码└(^o^)┘。

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

_.find,讨论这个函数首先要弄懂 _.findIndex_.findKey,这里我们先简单知道一个是针对数组一个是针对对象,具体的后面读到源码再说。传入值 obj 进行 isArrayLike 判断以此决定 keyFinder 函数,将三个参数包括回调传入 keyFinder 中其中 predicate 回调函数充当迭代器进行真值检测,最后 return obj[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;
        };
      };

_.findIndex 为例简单介绍一下,_.findIndex 是由 createPredicateIndexFinder 包装而成,意义在于返回 predicate 函数内部 return true。

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

_.filter 函数与 _.find 类似,内部实现较之 _.find 更简单些,_.find 意为匹配 predicate 回调 return true 唯一就近值,_.filter 则是匹配所有值的集合。那么有人说为什么不用 _.filter()[0] 取代 _.find,理论上二者确实是相同值,但是 _.filter 会遍历传参 obj 直至结束,而 _.find 则是遍历过程中匹配成功结束遍历,所以某些情况下 _.find 优于 _.filter

  _.reject = function(obj, predicate, context) {
    return _.filter(obj, _.negate(cb(predicate)), context);
  };

_.reject,通过 _.negatecb 函数包装 predicate 回调,实际上就是用 optimizeCb 优化 predicate function,然后用 _.negate 返回与 predicate 相反的 Boolean 类型值,以此获得与 _.filter 作用相反的结果集合。

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

_.every,我们看源码中的返回值类型为 Boolean 知道这是一个用于真值检测的函数,内部的处理步骤已经很程序化了,首先优化回调函数 predicate,处理传参 obj(根据 Object 或者 Array),回调中接收 obj[currentKey], currentKey, obj 三个参数进行 Boolean 判断,当判断失败的时候则 if (!false) return false; 结束 for 循环。这个方法看上去很鸡肋,但实际上结合 predicate 回调应用于某些判断处理很给力。

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

_.some,看源码我们可以知道它基本上与 _.every 类似,区别在于 _.some 遍历 obj 过程中只要任何一个元素通过 predicate 回调的真值检测就直接立即中断遍历并返回 true。我主观意识上更偏向于 _.every_.some 用一个相同的基础函数包装再通过判断值构建它们,就像 createReduce 函数构成 _.reduce_.reduceRight 一样,但是不知道作者为什么没有这样做,可能有其他的考虑吧,这里不再揣测。

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

_.contains 用于检查 obj 中是否包含 item 值,我更倾向于这是一个简化版的 _.some,如果是我写基础函数可能真的就只有 _.some 不用 _.contains,但是 Undescore.js 作为一个知名函数库,在代码优化的执行速度上肯定要比我们做的更细。
这里顺便说一下 _.indexOfguard_.indexOf 是由 createIndexFinder 包装而来,可以理解为数组版的 indexOf,indexOf 概念可参考 String.prototype.indexOf() 和 Array.prototype.indexOf()。关于 array.indexOf(searchElement[, fromIndex = 0]),我这里再说几句,这个 JAVASCRIPT 函数传入1或2个参数,第一个参数为将要进行匹配的内容,可为 Number 可为 String,第二个可选参数为(需要定向匹配数组中某一值的数组下标值 - array.length)*n,且 n!= 0array.indexOf 根据这个下标进行定向匹配验证,如果匹配成功则返回值为被匹配值的数组下标,匹配失败则返回 -1。

    var array = [2, 9, 9,9,9,3,4];
    undefined
    array.indexOf(9,2);
    2
    array.indexOf(9,3);
    3
    array.indexOf(9,4);
    4
    array.indexOf(9,5);
    -1
    array.indexOf(3,5);
    5
    array.indexOf(5);
    -1
    array.indexOf(2, -7);
    0

_.indexOf 虽然与 array.indexOf(searchElement[, fromIndex = 0]) 有所区别,但也有很多相通之处。

  _.invoke = restArgs(function(obj, method, args) {
    var isFunc = _.isFunction(method);
    return _.map(obj, function(value) {
      var func = isFunc ? method : value[method];
      return func == null ? func : func.apply(value, args);
    });
  });

_.invoke 用于批量执行方法,前面我们讲了 restArgs 方法,虽然代码很复杂,但目前实际上只应用了如下简化的结构:

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

也就是说 _.invoke 抛开闭包的概念之后等同于:

    function(obj, method, args) {
        var isFunc = _.isFunction(method);
        return _.map(obj, function(value) {
          var func = isFunc ? method : value[method];
          return func == null ? func : func.apply(value, args);
        });
      }

其中 _.isFunction 是判断是否为 function,接下来 _.map 回调,实际上我很纳闷万一传入的 method 是 obj[i] 对象上没有的方法怎么办,按照 return 的结果如果没有则返回 func 也就是 null,总觉得这样返回缺少点什么。

  _.pluck = function(obj, key) {
    return _.map(obj, _.property(key));
  };

_.pluck 返回传入 obj 的 key 的集合,或者说 key 的集合有点武断,更具体点说是 obj 下第二层所包含 key 的值的集合,而第一层也就是 obj 可为 Object 或 Array,但 obj 中第二层必须是 Object。这是为什么呢?

    _.map(obj, function(key) {
        return (function(obj) {
          return obj == null ? void 0 : obj[key];
        })(key);
      })

在上述简化的代码中我们可以看出 return obj == null ? void 0 : obj[key]; 的值是 obj[key],所以第二层只能是 Object。

  _.where = function(obj, attrs) {
    return _.filter(obj, _.matcher(attrs));
  };

_.where 很有趣,代码简化之后是:

  _.where = function(obj, attrs) {
    return _.filter(obj, (function(attrs) {
        attrs = _.extendOwn({}, attrs);
        return function(obj) {
          return _.isMatch(obj, attrs);
        })(attrs);
      });
  };

_.filter 我们讲过是获取所有匹配值的集合,而回调中的 _.extendOwn 将 attrs 放入空对象 {} 中并 return,_.isMatch是个断言用于判断 obj 中是否存在 key-value。那么 _.where 就是 _.isMatch_.filter 的加强版,它用于判断一个大的对象数组中存在与传入 attrs 相同的键值对,如果存在则返回匹配目标键值对所在的 Object,并且返回值是一个集合。

    var list = [{author:"Shakespeare",title:"china"},
        {author:"Shakespeare",year:1611,title:"china"},
        {author:"Shakespeare",year:1611,title:"English"},
        {year:1611,title:"china"}];
    _.where(list, {author: "Shakespeare", year: 1611});
    [{"author":"Shakespeare","year":1611,"title":"china"},{"author":"Shakespeare","year":1611,"title":"English"}]

这个方法在处理数据的时候特别有用。

  _.findWhere = function(obj, attrs) {
    return _.find(obj, _.matcher(attrs));
  };

_.findWhere,相当于 _.where()[0],即返回结果集合的第一个值,这么设定的目的和 _.find_.filter 一样,运算更快,遍历到目标马上停止遍历。

  _.max = function(obj, iteratee, context) {
    var result = -Infinity, lastComputed = -Infinity,
        value, computed;
    if (iteratee == null || (typeof iteratee == "number" && typeof obj[0] != "object") && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value != null && value > result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(v, index, list) {
        computed = iteratee(v, index, list);
        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
          result = v;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

_.max 用来查找 obj 对象数组中某一 key 的最大值的 Object,限定是 key-value 的 value 必须是 Number 类型。-Infinity 我更喜欢叫它负无穷,这里的 if true 第一个判断可以忽略了,为什么不讲了呢,因为作者要放弃 typeof iteratee == "number" && typeof obj[0] != "object" 这种情况,可见其他版本的 Underscore.js。如果忽略 typeof iteratee == "number" && typeof obj[0] != "object" 的情况则 _.max 传参为一个数组,return 为数组中最大值。if false 则进行常规的 _.each 代码很简单这里不再讲解。

  _.min = function(obj, iteratee, context) {
    var result = Infinity, lastComputed = Infinity,
        value, computed;
    if (iteratee == null || (typeof iteratee == "number" && typeof obj[0] != "object") && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value != null && value < result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(v, index, list) {
        computed = iteratee(v, index, list);
        if (computed < lastComputed || computed === Infinity && result === Infinity) {
          result = v;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

_.min 真心不用讲了,参考 _.max

  _.shuffle = function(obj) {
    return _.sample(obj, Infinity);
  };

_.shuffle 官网释义是返回一个随机乱序的 list 副本, 使用 Fisher-Yates shuffle 来进行随机乱序.Fisher-Yates shuffle 是什么鬼,我们这里看到 _.shuffle 这个函数用到了 _.sample,所以我们先讲 _.sample

  _.sample = function(obj, n, guard) {
    if (n == null || guard) {
      if (!isArrayLike(obj)) obj = _.values(obj);
      return obj[_.random(obj.length - 1)];
    }
    var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
    var length = getLength(sample);
    n = Math.max(Math.min(n, length), 0);
    var last = length - 1;
    for (var index = 0; index < n; index++) {
      var rand = _.random(index, last);
      var temp = sample[index];
      sample[index] = sample[rand];
      sample[rand] = temp;
    }
    return sample.slice(0, n);
  };

_.sample 是从一个 obj 中随机返回值,并且返回值受限于 n 这个参数,如果没有传入 n 或者传入了 guard = true 则执行 if 语句,目的是将 obj 判断处理之后返回单一值。这里觉得特鸡肋有木有,也就是说 _.sample(obj,n,true)_.sample(obj) 是一回事。如果按照 _.sample(obj,n) 的逻辑执行,依赖是老套路,处理 obj (Object 和 Array),然后 n = Math.max(Math.min(n, length), 0); 获得合理的 n 值,前面我们讲到了 Infinity 正无穷和 -Infinity 负无穷,这段代码利用了 Infinity 的特性包装了 _.shuffle函数,关键就是 Infinity 大于所有 Number 数字,即 Math.min(Infinity, Number) 等于 Number,好处就是让人眼前一亮,哇,原来代码还可以这样写,坏处就是当多带带使用 _.sample 函数的 n 大于处理之后的 obj 的长度时并不会报错,而是默认执行 n=sample.length,仁者见仁,智者见智吧。后面就是很套路的根据数组下标替换数组内容,当然数组下标是通过 _.random 随机的,然后 slice 一刀切数组。

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

转载请注明本文地址:https://www.ucloud.cn/yun/86328.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 面纱 - 第二章

    摘要:第四个判断如果是对象执行返回一个断言函数,用来判定传入对象是否匹配指定键值属性。都不匹配最后执行,返回传入的对象的属性。设置的值并生成函数,等同于,使具有属性且有值则返回,否则返回,这是一个判断函数。 在第二小章节里面我按照源码顺序介绍几个方法,源码紧接着第一章继续: var builtinIteratee; builtinIteratee,内置的 Iteratee (迭代器)。...

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

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

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

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

    v1 评论0 收藏0

发表评论

0条评论

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