本文將討論使 JavaScript 函數更有表現力的兩個特性:Rest 參數和參數預設值。
Rest 參數
通常,我們需要建立一個可變參數的函數,而可變參數是指函數可以接受任意數量的參數。例如,String.prototype.concat 可以接受任何數量的字串作為參數。使用 Rest 參數,ES6 為我們提供一種新的方式來建立可變參數的函數。
我們來實作一個範例函數 containsAll,用來檢查一個字串中是否包含某些子字串。例如,containsAll("banana", "b", "nan") 將會傳回true,containsAll("banana", "c", "nan") 將會傳回 false。
以下是傳統的實作方式:
function containsAll(haystack) { for (var i = 1; i < arguments.length; i++) { var needle = arguments[i]; if (haystack.indexOf(needle) === -1) { return false; } } return true; } function containsAll(haystack) { for (var i = 1; i < arguments.length; i++) { var needle = arguments[i]; if (haystack.indexOf(needle) === -1) { return false; } } return true; }
實作用到了 arguments 對象,該物件是一個類別數組對象,包含函數被呼叫時的實參列表。這段程式碼正是我們想要的,但其可讀性卻不是最優的。函數只有一個形參haystack,所以不可能一看就知道該函數需要多個參數,並且在遍歷arguments 時,需要特別注意遍歷的開始索引為1 ,而不是常見的0,因為arguments[0] 是函數定義時的形參haystack。如果我們想在 haystack 參數之前或之後加入一些參數,我們必須更新內部的循環。 Rest 參數解決了這些問題,以下是 使用 Rest 參數的實作方式:
function containsAll(haystack, ...needles) { for (var needle of needles) { if (haystack.indexOf(needle) === -1) { return false; } } return true; } function containsAll(haystack, ...needles) { for (var needle of needles) { if (haystack.indexOf(needle) === -1) { return false; } } return true; }
以上兩個實作都滿足了我們的需求,但後者包含一個特殊的 ...needles 語法。讓我們來看看調用containsAll("banana", "b", "nan") 時的細節,參數haystack 和以往一樣,將用函數的第一個實參填充,值為"banana",needles 前面的省略號表示它是一個Rest 參數,剩餘的所有實參將被放入數組中,並將該數組賦給needles 遍量。在這個呼叫中,needles 的值為 ["b", "nan"]。然後,就是正常的函數執行了。
只能將函數的最後一個函數作為Rest 參數,在函數被呼叫時,Rest 參數之前的參數都會被正常填充,之外的參數將被放入一個數組中,並將該數組作為Rest參數的值,如果沒有更多的參數,那麼Rest 參數的值為一個空數組[],Rest 參數的值永遠不會是undefined。
參數的預設值
通常,當呼叫函數時,不需要呼叫者傳遞所有可能的參數,那些沒有傳遞的參數都需要一個合理的預設值。 JavaScript 對那些沒有傳遞的參數都有一個固定的預設值 undefined。在 ES6 中,引入了一種新方法來指定任意參數的預設值。
看下面範例:
function animalSentence(animals2="tigers", animals3="bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; } function animalSentence(animals2="tigers", animals3="bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; }
在每個參數的 = 後面是一個表達式,指定了參數未傳遞時的預設值。所以,animalSentence() 返回"Lions and tigers and bears! Oh my!", animalSentence("elephants") 返回"Lions and elephants and bears! Oh my!", animalSentence("elephants", "whales") 返回"Lions and elephants and whales! Oh my!"。
參數預設值需要注意的幾個細節:
與 Python 不一樣的是,參數預設值的表達式是在函數呼叫時從左到右計算的,這表示表達式可以使用前面已經被填入的參數。例如,我們可以將上面的函數變得更有趣一點:
function animalSentenceFancy(animals2="tigers", animals3=(animals2 == "bears") ? "sealions" : "bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; } function animalSentenceFancy(animals2="tigers", animals3=(animals2 == "bears") ? "sealions" : "bears") { return `Lions and ${animals2} and ${animals3}! Oh my!`; }
那麼,animalSentenceFancy("bears") 將會回傳 "Lions and bears and sealions. Oh my!"。
傳遞 undefined 等同於沒有傳遞該參數。因此,animalSentence(undefined, "unicorns") 將會傳回 "Lions and tigers and unicorns! Oh my!"。
如果沒有為一個參數指定預設值,那麼該參數的預設值為 undefined,所以
function myFunc(a=42, b) {...} function myFunc(a=42, b) {...}
等同於
function myFunc(a=42, b=undefined) {...} function myFunc(a=42, b=undefined) {...}
拋棄 arguments
透過 Rest 參數和參數的預設值,我們可以完全拋棄 arguments 對象,使我們的程式碼可讀性更高。此外,arguments 物件也加深了優化 JavaScript 的難題。
希望以上兩個新特性可以完全取代 arguments。作為第一步,在使用Rest 參數或參數的預設值時,請避免使用arguments 對象,假如arguments 對象還不會立即被移除,或者永遠不會,那麼也最好是避免在使用Rest 參數或參數預設值時使用arguments 物件。
相容性
Firefox 15 以上的版本已經支援這兩個新特性。然而,除此之外,還沒有其他任何瀏覽器支援。最近,V8 的實驗環境增加了對 Rest 參數的支持,而參數預設值還有一個 issue,JSC 也對 Rest 參數和參數預設值提了一些 issue。
Babel 和 Traceur 這兩個編譯器都已經支援了參數預設值,所以你可以大膽使用。
結論
儘管從技術層面上看,這兩個新特性在並沒有給函數引入新的行為,但它們可以使一些函數的聲明更具表現力和可讀性。