在第二小章节里面我按照源码顺序介绍几个方法,源码紧接着第一章继续:
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 < 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 这个参数,那么没有 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
,而 _.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 < 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
计算顺序相反即从右面向左面开始。