Underscore 源码的学习落下了好几天,因为前几天一直正在重构项目和搞 React,不过这几天应该会花较多时间在 Underscore 上面了。
这次主要说下 Underscore 两个比较重要的函数吧,一个是optimizeCb
,另一个是cb
,这两个花了我挺长时间看的,而且是整个 Underscore 非常重要的函数,后面很多地方都使用到了它。
optimizeCb 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; switch (argCount == null ? 3 : argCount) { case 1: return function(value) { return func.call(context, value); }; case 3: return function(value, index, collection) { return func.call(context, value, index, collection); }; case 4: return function(accumulator, value, index, collection) { return func.call(context, accumulator, value, index, collection); }; } return function() { return func.apply(context, arguments); }; };
|
这个地方 switch
只是一个性能的优化,其实简化来看就是这样的
1 2 3 4 5 6
| var optimizeCb = function(func, context, argCount) { if (context === void 0) return func; return function() { return func.apply(context, arguments); }; };
|
之所以有那段 switch
前面一篇已经有提到了,只是一个优化而已。使用 call 快于 apply。不过好像最新的 Chrome 已经可以自己优化这个过程,但为了提升性能,加上也无妨。
解释下段代码的意思,字如起名 optimizeCb 优化回调。这个函数传入三个参数依次是函数,上下文,参数个数。如果没有指定上下文则返回函数本身,如果有,则对该上下文绑定到传入的函数,根据传入的参数个数,在做一个性能优化。这个函数就是这个意思。我们看下他的使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| _.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; };
|
这个函数是用来实现数组或者对象的遍历的,他是怎么做到呢?
首先是
1
| iteratee = optimizeCb(iteratee, context);
|
这个是优化 iteratee
这个函数,如果指定了上下文(context
)则做绑定。一开始没理解 iteratee
这东西,其实他就是一个函数而已,比如
1 2 3 4 5 6 7 8 9 10 11 12
| var stu = { 'age': 20, 'school': 'SCNU', 'sex': 'male' }; _.each(stu, function(value, key, obj) { console.log(key + ' : ' + value); }); age : 20 school : SCNU sex : male
|
我们传入了一个函数,这个函数可以有三个回调参数分别是 value, key, obj 分别表示键值,键名,迭代对象。
重新看回 each
这个函数,isArrayLike
函数判断 obj
是不是数组,如果是的话,一个循环分别把 obj[i]
, i
, obj
分别传入这个 iteratee
这个传入来的函数,比如上面的 function(value, key, obj){}
里面,一一对应到 value
, key
, obj
。从而实现迭代。在下面的是对象的处理,没什么好说的。
然后我们就讲完了 optimizeCb 这个函数了,其实也挺好理解的。
cb 函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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); };
|
首先是 buildinIteratee
这东西,这要结合下面这个来看
1 2 3
| _.iteratee = builtinIteratee = function(value, context) { return cb(value, context, Infinity); };
|
这个是给用户自定义迭代规则用的。怎么自定义呢,比如这样
1 2 3 4 5 6
| _.iteratee = function(value, context) { if (value == null || _.isObject(value)) return _.identity; if (_.isFunction(value)) return optimizeCb(value, context, argCount); return _.property(value); }
|
还有 identity
其实就这样,返回一个返回自身的函数…
1 2 3 4
| _.identity = function(value) { return value; };
|
我们自定义的这个迭代规则,如果 value
不为空而且是对象,则返回一个可以返回自身的函数。注意我们改变的只是 iteratee
函数,builtinIteratee
存的是默认规则,在 cb
函数中如果发现 iteratee
的行为更改了,则使用更改的行为来处理,否则往下默认处理,上面已经备注的很清楚了,自己看吧。
我们举个例子说下 cb
函数的用法,例如
1 2 3 4 5 6 7 8 9 10 11
| _.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 的,比如
1 2 3
| var results = _.map([1, 2, 3], function(value, index, obj) { return '['+obj+']' + '\'s '+index+' position is '+value; });
|
我们使用 map 传入两个参数,一个是迭代对象,这里是 [1, 2, 3]
,第二个参数是迭代函数,这里是 function (value, index, obj){...}
。这个函数在 map 内部也就是 iteratee
,然后我们再来看
1
| iteratee = cb(iteratee, context)
|
iteratee
是一个函数,使用上面这个句子返回了 optimizeCb(value, context, argCount)
,这里的 value
就对应了我们的 function(value, index, obj){...}
函数。接着回到 map,他对对象进行遍历依次把通过调用 iteratee
也就是我们传入的函数得到的结果复制给 result
,最后返回了 result
。
所以上面例子的结果是
1 2 3 4 5
| results: [ "[1,2,3]'s 0 position is 1", "[1,2,3]'s 1 position is 2", "[1,2,3]'s 2 position is 3" ];
|
一定要理解 cb 和 optimizeCb 这两个的用法,他们在后面多次用到。好了,完了~