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(),就知道这一函数的作用之大。在 underscore 中 return 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 +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
等同于:

  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 _.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 Map、Map.prototype、WeakMap 用于知识储备,至于作者的
.map 更多的是根据一定的条件遍历 obj 中的元素,与 .forEach 的更大区别是 .forEach 不会对传入的 obj 做改动直接 return obj,而 _.map 会 return results,return 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 = 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 计算顺序相反即从右面向左面开始。

关键字:JavaScript, web, WEB前端开发


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部