我们去看underscore源码的时候会发现,特别多的地方出现了cb, optimizeCb,iteratee等函数,我一开始看到这些也很迷惑,感觉绕来绕去,但这些就是一个开源库的健壮基石。
OptimizeCb
先来看看最简单的一个,源码如下
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);
};
// 因为underscore没有2个参数的情况
// The 2-argument case is omitted because we’re not using it.
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);
};
};
其实这个函数看起来一大堆,作用就一个,就是为了避免使用arguments。
第三个参数argCount就是func用到的参数个数,然后用个function包装一下,里面将参数一个个分好传给原始func
为什么没有argCount为2的情况呢?
因为搜索源码里用到optimizeCb的地方就会发现,没有一个地方是穿2个参数的场景,所以将这种情况去掉了
为什么要不使用arguments呢?
其实就是为了提高一点性能,开源库就是喜欢加这种东西
cb
cb的代码看起来不多,主要内容就是对value做一些兼容性的处理,代码如下
// An internal function to generate callbacks that can be applied to each
// element in a collection, returning the desired result — either `identity`,
// an arbitrary callback, a property matcher, or a property accessor.
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) && !_.isArray(value)) return _.matcher(value);
return _.property(value);
};
我们看underscore的文档会发现,很多API的参数要求很宽松,既可以传function,也可以传Array或是string,其实背后往往都是cb作了处理。
我们看一下执行该方法时候的逻辑
1. 判断iteratee方法是否有被修改,如果被修改则调用它并返回
我们看内部定义的iteratee方法
_.iteratee = builtinIteratee = function(value, context) {
return cb(value, context, Infinity);
};
刚好就是调用cb,所以如果没有修改的话,不用再处理一次
2. 判断是否有传入value,如果没有传值,则返回自身的identity方法
// Keep the identity function around for default iteratees.
_.identity = function(value) {
return value;
};
其实就是你使用map,filter之类的循环方法时,没有传需遍历执行的`function``,那我就只能把值原封不动的返回给你了,所以用到了这个方法
3. 判断传入的value是不是一个function,是的话调用optimizeCb处理一下
就跟上面讲的一样,如果你传入的是一个最正常的function,就简单的使用optimizeCb方法处理一下,提升一下性能
4. 判断value是不是一个object,是的话调用matcher方法
// 返回一个断言函数,这个断言函数来辨别数组元素是否匹配制定键值属性
_.matcher = _.matches = function(attrs) {
attrs = _.extendOwn({}, attrs);
return function(obj) {
return _.isMatch(obj, attrs);
};
};
这个方法会返回一个用来判断两个对象键值对是否匹配的方法
5. 如果都没有匹配到,则调用property并返回
一般走到这里的value都是string类型,而property就是取对应key值的方法
_.property = function(path) {
if (!_.isArray(path)) {
return shallowProperty(path);
}
return function(obj) {
return deepGet(obj, path);
};
};
这时候就相当于会遍历取值返回
结语
对于一般的业务项目,都有比较固定的场景,不会对于传参的各种可能情况做处理,但对于一个开源库来说,多写这类没有实际逻辑功能的支撑方法,对于提升代码的健壮性是很有作用的。 我们在一些大型项目中也可以借鉴参考