2015-12-25

Javascript 的 new

我們知道 Javascript function 有兩種呼叫方式,一種是直接呼叫,另一種是透過 new 呼叫,兩種呼叫方式有什麼差別呢?就我目前所知,有兩個差異。

第一個差異是 this 是誰?

function WhoIsThis() {
  console.log(window == this);
  console.log(this instanceof WhoIsThis);
}
如果是用 function 方式呼叫,會得到 true 與 false,表示以 function 方式呼叫時,「主體」是 window,這當然是以瀏覽器為執行環境。
WhoIsThis(); // true, false
但是改用 new 方式呼叫,會得到相反地結果,false 與 true,表示以 new 方式呼叫時,「主體」是 function 本身建立的物件,也就是 WhoIsThis 的 instance。
new WhoIsThis(); // false, true

第二個差異是 return 什麼?

function ReturnWhat() {
}
console.log(ReturnWhat()); // undefined
console.log(new ReturnWhat()); //ReturnWhat {}
以 function 方式呼叫時,由於沒有定義 return,所以回傳的當然是 undefined,但是以 new 方式呼叫時,即使沒有定義 return,卻還是回傳了 ReturnWhat 的 instance。

這是由於 Javascript 在 new 方式呼叫時會「自動」加上 return this

疑?自動?那麼來搗蛋一下,如果有定義 return 但是用 new 方式呼叫會發生什麼事?
function ReturnMe() {
  return new Date();
}
console.log(ReturnMe());
console.log(new ReturnMe());
以 function 方式呼叫時得到當下時間,但是以 new 方式呼叫時,也是得到當下時間,疑?回傳的到底是 ReturnMe 還是 Date 的 instance?
console.log((new ReturnMe()) instanceof ReturnMe);
console.log((new ReturnMe()) instanceof Date);
結果是 false 與 true,得到的是 Date 的 instance,該怎麼解釋呢?其實很簡單,用 new 呼叫沒有定義 return 的 function 時,Javascript 會自動加上 return this,但是用 new 呼叫有定義 return 的 function 時,Javascript 什麼都不做,就照實 return,所以上面的例子會得到 Date 的 instance。

這裡要注意的是,用 new 呼叫得到回傳的 instance 可能和所呼叫的 function 完全沒關係,完全視 return 而定,這裡和 Java 很不一樣。

以上兩個差異是我目前所知的差別,接下來就專心來看看 new 吧。

如果以 new 的思維來定義 function(也就是在 function 裡使用 this 儲存變數),但是卻被用 function 的方式呼叫了,會發生什麼事?
function Tree(title) {
  this.title = title; // 將傳進來的 title 存在 Tree instance 裡
}
var t = new Tree('Book'); // 正確的使用 new 方式呼叫
console.log(t.title); // Book

var t = Tree('Video'); // 使用錯誤的 function 方式呼叫
console.log(t.title); // error,因為 Tree 沒有定義 return,用 function 方式呼叫不會自動加上 return this,所以 t 是 undefined。
那 Video 跑去哪兒呢?
console.log(this.title); // Video
console.log(window.title); // Video
在 window 上,原因是前面提過的第一點差異,function 的主體是 window,這可能是很危險的情況,因為如果 window 剛好有 title 變數的話,就會被覆蓋掉,title 死的不明不白,還很難除錯。

自動校正錯誤的 new function 呼叫

那要如何避免誤用呢?如果是自己寫得自己用,還誤用就自己該死,但是如果是要公開的 API 呢?

有兩種方法可以避免 new function 被誤用,兩種的概念都是用 instanceof 去判斷「主體」是誰?然後用不同的方式去修正錯誤的使用情形。

第一種是轉呼叫正確的 new function。
function Tree(title) {
  if (!(this instanceof Tree)) {
    return new Tree(title); // 發現主體不對,就轉呼叫正確的方式
  }
  this.title = title;
}

var t = new Tree('Book');
console.log(t.title); // Book

var t = Tree('Video');
console.log(t.title); // Video

第二種是利用 ES5 的 Object.create() 來建立正確的 instance。
function Tree(title) {
  var that;
  if (this instanceof Tree) {
    that = this; // 正確的呼叫方式
  }
  else {
    that = Object.create(Tree.prototype); // 使用 ES5 的 Object.create() 來建立正確的 instance。
  }
  that.title = title;
  return that; // 記得最後要回傳自訂的 instance
}

var t = new Tree('Book');
console.log(t.title); // Book

var t = Tree('Video');
console.log(t.title); // Video
---
---
---

沒有留言:

張貼留言