[思] 當需要傳遞多個不定引數時,該如何設計 JavaScript 函式?

aleen42發表於2019-02-28

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);
};複製程式碼

相關文章