在第二小章节里面我按照源码顺序介绍几个方法,源码紧接着第一章继续:html
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 属性。app
_.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 中的传入形参的数量减一,举个例子如:ide
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 而已。ui
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 这个空函数建立,没什么可细说的。this
var property = function(key) { return function(obj) { return obj == null ? void 0 : obj[key]; }; };
property 用于获取 obj 的 key 值,经过 property()
设置 key ,重点是设置
两个字,有 key 则以没有则建立之。prototype
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
计算顺序相反即从右面向左面开始。