Issue 地址:github.com/aleen42/Per…
其實我們很多時候在設計一個 JavaScript 函式時,都可能會遇到這樣的一種情況:引數數量不定,且一般會傳遞有多個。此時,我們就會想該如何更優雅地設計函式來接收這些不定的引數?此 Issue 的提出就是為了談談不才及劣者所知道的一些見解。
對於這樣的情況,我們肯定會想到一對函式:
同樣是為方法呼叫指定 this
,兩者的區別在於傳遞不定引數的方式。
為了方便記憶,我通常會採用一種巧妙的方法去區分兩者。
call()
首字母為 c,因而可看作以逗號(Comma)的方式來區分引數,而apply()
首字母為 a,因而也可看作是以陣列(Array)的方式來區分引數。
這樣一看,官方似乎已為我們預先提供了兩種通用的方式:
- 以逗號分隔引數
- 以陣列組合引數
然而,若不去親身實現還不知道該怎麼設計函式才能更為優雅?對此,不才與劣者認為應該先實現陣列組合的方式,而後再實現逗號分隔的方式。為了能更好地說明,我將舉例 underscore 中關於 _.without()
與 _.difference()
的實現。在討論實現之前,我們先了解這兩個函式到底有何作用?其實,它們主要用於過濾陣列中的部分成員。因此,通過下面的程式碼片段我們就能清晰地看到:
var arr = [1, 2, 3, 4, 5];
var result = _.without(arr, 1, 2, 3);
console.log(result); /** => [4, 5] */複製程式碼
var arr = [1, 2, 3, 4, 5];
var result = _.difference(arr, [1, 2, 3], [5, 6]);
console.log(result); /** => [4] */複製程式碼
過濾成員需要通過不定的引數來告知函式,而兩者唯一的區別與前述例子類似,也就是傳遞不定引數的方式不同。那麼,回到原來的問題,為何我們要先設計並實現以陣列組合方式的函式呢?其實很簡單,原因在於反過來實現會造成許多不必要的麻煩。
例如我們先實現陣列方式傳遞的 _.difference()
,我們就可以通過簡單的陣列組合來實現 _.without()
:
_.difference = function (array) {
/**
* 把後續的引數嚴格鋪平成一個陣列,即忽略不是包含在陣列內的引數
* 如 _.difference(array, [1, 2], 3); 語句中的 3
*/
var rest = flatten(arguments, true, true, 1);
return _.filter(array, function (value) {
return !_.contains(rest, value);
});
};
_.without = function (array) {
return _.difference(array, Array.prototype.slice.call(arguments, 1));
};複製程式碼
但倘若反過來,實現方式則變得更為複雜:
_.without = function (array) {
/** 若經過 `_.difference()` 的呼叫,則還需要把引數進行一層鋪平 */
var rest = (this == `difference`) ?
_.flatten(arguments, false, false, 1) :
Array.prototype.slice.call(arguments, 1);
return _.filter(array, function(value) {
return !_.contains(rest, value);
});
};
_.difference = function(array) {
var args = _.flatten(arguments, true, true, 1);
return _.without.apply(`difference`, args.unshift(array));
};複製程式碼
綜上所述,以 _.without()
和 _.difference()
為例其複雜點顯然在於 _.without()
該如何區分到底是否經過 _.difference()
來呼叫自己?因為針對這樣的兩種情況,_.without()
都需要對引數進行不同的處理。簡單來說,_.without()
對於來自 _.difference()
的呼叫需要再進行一次鋪平(注:不才與劣者此處是通過指定 this
來提供一種區分的方式)。為何?細緻想想就會發現,產生如此的複雜在於舊版的 JavaScript 語法只能通過陣列組合來傳遞若干個不定的引數,而無法鋪開成逗號分隔的形式來傳遞。
那麼,既然語法存在缺陷,ES6 是否提供了新的方式去解決該問題呢?不才認為,這恰恰體現出展開操作符(...
,Spread Operator)的魅力所在。有了它,你就可以直接展開若干個不定引數呢!
_.without = function (array) {
var rest = Array.prototype.slice.call(arguments, 1);
return _.filter(array, function(value) {
return !_.contains(rest, value);
});
};
_.difference = function(array) {
var args = _.flatten(arguments, true, true, 1);
args.unshift(array);
return _.without.call(null, ...args);
};複製程式碼