先看一下兩個比較重要的內部函數
~~~
63 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 2: return function(value, other) {
return func.call(context, value, other);
};
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);
};
};
~~~
這個函數是underscore內部很重要的函數,主要用來執行函數并改變所執行函數的作用域,最后加了一個argCount參數來指定參數個數,對參數個數小于等于4的情況進行分類處理。對不同參數的解釋大概是:
1的情況一般是用在接受單值的情況,比如times,sortedIndex之類的函數。
2的情況據說是給比如jQuery,zepto事件綁定,代理什么的,但是在源代碼中沒有看到被調用。
3的情況用于迭代器函數,比如foreach,map,pick等。
4的情況用reduce和reduceRight函數。
~~~
87 var cb = function(value, context, argCount) {
if (value == null) return _.identity;
if (_.isFunction(value)) return optimizeCb(value, context, argCount);
if (_.isObject(value)) return _.matcher(value);
return _.property(value);
};
~~~
這也是一個比較常用的內部函數,只是對參數進行了判斷:如果是函數則返回上面說到的回調函數;如果是對象則返回一個能判斷對象是否相等的函數;默認返回一個獲取對象屬性的函數。
~~~
140 _.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;
};
// Return the results of applying the iteratee to each element.
_.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;
};
~~~
從代碼上看,each函數是用來遍歷操作數組/對象,map用來處理數組/對象后并以數組的形式返回結果。至于forEach和collect在API文檔中看不到,應該是為了兼容以前老版本做的別名處理。
~~~
170 function createReduce(dir) {
// Optimized iterator function as using arguments.length
// in the main function will deoptimize the, see #1991.
function iterator(obj, iteratee, memo, keys, index, length) {
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) {
iteratee = optimizeCb(iteratee, context, 4);
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1;
// Determine the initial value if none is provided.
if (arguments.length < 3) {
memo = obj[keys ? keys[index] : index];
index += dir;
}
return iterator(obj, iteratee, memo, keys, index, length);
};
}
~~~
這個是reduce和reduceRight調用的內部函數,將memo這個變量作為入參傳遞給iterator函數,調用自定義的iteratee函數進行循環處理,每次處理完的結果都賦值給memo變量,最后返回memo變量的結果。這里有兩個問題
* 為什么這里不按照常理邏輯來寫代碼而要用閉包呢?閉包大致有這么幾個作用:避免命名沖突;私有化變量;變量持久化。這里的作用主要就是變量(函數)持久化,好處就是重復調用的時候不需要再重新創建函數,從而提升執行速度。
* 為什么要用兩層閉包呢?第一層閉包持久化iterator函數,調用reduce和reduceRight函數避免重復新建函數。第二層閉包保存keys,index,length這些變量。
263 _.invoke = function(obj, method) {
~~~
var args = slice.call(arguments, 2);
var isFunc = _.isFunction(method);
return _.map(obj, function(value) {
var func = isFunc ? method : value[method];
return func == null ? func : func.apply(value, args);
});
~~~
};
這里用`slice.call(arguments, 2)`來獲取后面的不定參數,然后用`func.apply(value, args)`來傳入該參數比較有意思。