雖然Underscore並無在API手冊中說起到restArgs函數,咱們仍然能夠經過_.restArgs
接口使用restArgs函數。若是不去閱讀源碼,咱們很難發現Underscore中還有這樣的一個函數,對於這樣的一個「沒有存在感」的函數,咱們爲何要使用並學習它呢?html
這個函數雖然比較「低調」,可是它在Underscore中的存在感卻一點也不低。在Underscore源碼中,restArgs函數做爲工具函數,參與多個公開API的實現,可謂勞苦功高。從其屢次參與實現公開API能夠看出,這是一個十分重要的函數,爲了方便講解後面的公開API,這裏專門寫一篇文章介紹restArgs工具函數。git
在現實中,咱們可能有碰到過一些特殊狀況,好比咱們所寫的函數不肯定有多少個要傳遞的參數,這樣在函數內部實現參數處理時就會比較棘手。es6
好比如今咱們須要構建一個函數,這個函數接受至少兩個參數,第一個是一個數組對象,第二個以後是一些值,咱們的函數就須要把這些值添加到第一個參數的尾部。github
代碼實現:數組
function appendToArray(arr) {
if(arguments.length < 2)
throw new Error('funciton a require at least 2 parameters!');
return arr.concat(Array.prototype.slice.call(arguments, 1));
}
複製代碼
如今咱們須要實現另外一個函數,該函數也是把參數附加到數組中,不過實現了過濾器功能,好比只把大於1的參數附加到數組中。瀏覽器
代碼實現:app
function appendToArrayPlus(arr, filter) {
if(arguments.length < 3)
throw new Error('function a require at least 3 parameters!');
var params = Array.prototype.slice.call(arguments, 2);
for(var i = 0; i < params.length; i++) {
if(filter(params[i])) {
arr.push(params[i]);
}
}
return arr;
}
複製代碼
能夠看出來,咱們在開發這兩個函數時,作了重複的工做,那就是根據多餘參數的開始序號來截斷arguments對象。框架
這樣的作法,在寫一兩個函數時沒有什麼問題,可是在開發框架時,須要寫大量的參數個數不肯定的函數,這就會使得冗餘代碼大量增長,而且屢次直接操做arguments對象的作法並不十分優雅。函數
因此咱們須要一個restArgs這樣的工具函數,給它傳遞一個函數以及一個多餘參數開始索引(startIndex)做爲參數,它會返回一個函數,咱們在調用返回的函數時,開始索引以後的多餘參數會被放入到數組中,而後一併傳遞給restArgs的第一個參數函數調用(做爲最後一個參數)。工具
有人會說,ES6中已經實現了rest params的功能,參考阮老師教程,可是咱們知道一個框架的開發,必須考慮到兼容問題,不少低端瀏覽器並未實現ES6語法。因此在Underscore中,暫時還未使用ES6語法。
Underscore實現的源碼(附註釋):
// Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html)
// This accumulates the arguments passed into an array, after a given index.
//restArgs用於把func的位於startIndex以後的參數歸類爲一個數組,
//而後返回一個函數把這個數組結合startIndex以前的參數傳遞給func調用。
var restArgs = function (func, startIndex) {
//function.length表示function定義時,形式參數的個數。
//注意此處是func.length,即傳入的方法參數的形參個數而不是當前函數的參數個數,須要結合具體傳入的參數來看。
//當startIndex參數未傳遞時,默認func函數的最後一個參數開始爲多餘參數,會被整合到數組中。
startIndex = startIndex == null ? func.length - 1 : +startIndex;
return function () {
//length表示構造的多餘參數數組的長度,是實際的多餘參數或者0。
var length = Math.max(arguments.length - startIndex, 0),
rest = Array(length),
index = 0;
//新建了一個rest數組,把位於startIndex索引以後的全部參數放入該數組。
for (; index < length; index++) {
rest[index] = arguments[index + startIndex];
}
//將多餘參數放入rest數組以後,直接用Function.prototype.call執行函數。
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);
}
//若是startIndex > 2,那麼使用apply傳遞數組做爲參數的方式執行func。
//雖然調用方法發生了變化,可是仍是會把rest數組放在傳入的參數數組的最後。
//這樣作其實與以前的方法無異(switch部分能夠刪除),可是call的效率高於apply。
//args數組用於盛放startIndex以前的非多餘參數。
var args = Array(startIndex + 1);
for (index = 0; index < startIndex; index++) {
args[index] = arguments[index];
}
args[startIndex] = rest;
return func.apply(this, args);
};
};
複製代碼
能夠注意到兩個重點:
1 startIndex默認爲func函數的形參個數減1,那麼表明的含義就是當咱們調用restArgs函數不傳遞第二個參數時,默認從最後一個形參開始即爲多餘參數。
好比:
_.invoke = restArgs(function (obj, path, args) {...});
_.invoke(obj, path, 1, 2, 3);
複製代碼
以上代碼中,若是咱們給_.invoke傳遞這些參數,那麼實際上執行的函數會是:
function(obj, path, [1, 2, 3]) {
// ...
}
複製代碼
這樣咱們就能夠在寫函數_.invoke
時,很方便的預處理args數組。
2 switch中的內容只是後面func.apply(this, args)
的一個特例,不寫switch也徹底能夠實現功能,可是之因此要寫這個switch,是由於Function.prototype.call的效率要高於Function.prototype.apply(具體請參考:Why is call so much faster than apply?)。
學習完這個內部函數以後,再學習其餘API源碼時,就會好理解許多,咱們須要重點注意的一點就是當restArgs只接受一個函數做爲參數時,表示默認從接受的最後一個參數開始(包括最後一個參數)即爲多餘參數。
其他Underscore源碼解讀文章:GitHub