Javascript new with apply

  js 函式可以透過一些方式與公用函式來動態呼叫,並用陣列動態丟入參數,但是透過公用函式可以呼叫參數那不就失去能力動態呼叫 new 建構子了嗎?因為語法上有衝突~如同下面範例,第二行,這樣就已經變成呼叫函式了,如果變換一下用第三種方式呼叫,乍看好像結果會一樣,但是如果建構子內有修改全域變數可能會造成一些後遺症,那要如何用陣列丟參數的情況下產生物件勒勒勒~~

  // call function by array
  func.apply(this, [1, 2]);
  // new object by array ?
  new func.apply(this, [1, 2]);
  // maybe has side effect .. because the func be called twice.
  func.apply(new func(), [1, 2]);


js 在執行期動態決定呼叫的函式上有很多種支援方式,簡單的就是把變數存函式參照直接呼叫。

var hello = function(message) { alert('hello ' + message); }
hello('world!');

但是如果參數也是動態決定的,就沒有辦法用這種方式把參數寫在程式內,雖然可以用陣列來丟,但是就會失去一點原始用途的相容性,例如這個 add 函式。

//原本
function add(base, term) {
   return base + terms;
}
add(1, 2);
//陣列參數
function add(terms) {
  return terms[0] + terms[1];
}
add([0, 1]);

我就不希望他變成用陣列的方式傳,這樣被強迫改變我原本的 API。因此 JavaScript 提供了 apply 與 call 兩個參數來作這種動態呼叫函式並提供可傳陣列的參數。

讓我可以用原始的方式呼叫我原本有兩個參數的 add。例如:

 add.apply(this, [1, 2]);
 add.call(this, 1, 2);

  都可以像是用 add(1, 2) 一樣的傳參數方式來呼叫函式,實際上可以在 add 裡面用 console.log(add.arguments) 來觀察參數的情況。在這邊正常情況應該是要解釋一下 this 的用途,在 js 的函式內有可能會被當成建構子或是物件的 method 呼叫,因此都會有一個承載物件,要在函式內引用該物件的方式就是透過 this..(我以前好像有想寫過物件的文章只是沒寫好爛掉了= =),因此如果當成一般函式呼叫則不用在意這個 this,瀏覽器在全域的情況下通常都是 window。在 node.js 情況就是 global。

嗯…回到重點就是 call 跟 apply 的差別是 apply 可以用陣列當參數模擬一個一個丟入函式中,而不是只丟一個陣列的參數。但是 call 還是只能一個一個丟,但是他們都能改變 this。所以在這種情況下其實 apply 應該比較多功能一點。

  最後回到主題,要如何用陣列當參數建立一個物件呢。參考資料[2]裡面的最佳解答是找到比較合理的作法。這個公用函式專門讓你來做這件事,他幾乎是在模擬建立一個 javascript 物件的流程,先是呼叫建構子函式,在將生成出來物件的 prototype 都參照至該建構子的 prototype。只是委託了一個暫時的 仿類別建構子 ,這個 F 物件的私有函式與公開函式都跟被當參數傳進去的 constructor 一模一樣
,而且可以透過陣列來將參數傳到建構子內,這種作法算是閃避掉了對原始物件做 new 的行為,但是也不需要對原生類別做修改,還算可以接受的一個作法,但似乎也有一種看不太到的後遺症?像是每次動態產生的 F 是否會有額外的記憶體花費,是否要將 F 在全域中保存下來?

function construct(constructor, args) {
    function F() {
        return constructor.apply(this, args);
    }
    F.prototype = constructor.prototype;
    return new F();
}

//usage
construct(add, [1, 2]);

//or 
Function.prototype.new = construct;
add.new([1, 2]);

結論:

  JavaScript 沒有提供方式來改變 new 的行為,記得 python 連物件的生成都給予開發者機會控制(metaclass?),具有更大的彈性,且許多機制都透過 magic function 讓開發者有機會覆寫,架構考量完整許多。雖 JavaScript 似乎尚未想到更簡潔的語法來作到這件事,但 js 給人一種…也許只是你沒本事想到怎麼做而已的姿態。畢竟十年前應該沒人想到 js 有能力能發展到現在這樣子
吧!
參考資料:

  1. Function.prototype.call() and Function.prototype.apply()
  2. Use of .apply() with ‘new’ operator. Is this possible?

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *