你真的弄明白 new 了嗎

很久沒有寫點東西了,總以爲本身應該寫點牛逼的,卻又不知道如何下筆。既然如此,仍是迴歸最基本的吧,今天就來講一說這個new。關於javascript的new關鍵字的內容上網搜一搜還真很多,你們都說new幹了3件事:javascript

  • 建立一個空對象
  • 將空對象的__proto__指向構造函數的prototype
  • 使用空對象做爲上下文調用構造函數

文字比較難懂,翻譯成javascript:html

javascriptfunction Base() {
    this.str = "aa";
}

// new Base()幹了下面的事
var obj  = {};
obj.__proto__ = Base.prototype;
Base.call(obj);

想一想是這麼回事哈,那就趕快試試:java

javascriptvar b = new Base();
console.dir(b); // Base {str: 'aa', __proto__: Base}

好像是正確的,可是真的正確嗎???shell

真的就3件事?

每一個對象都有一個constructor屬性,那麼咱們來試試看new出來的實例的constructor是什麼吧。編程

javascriptconsole.dir(b.constructor); // [Function: Base]

能夠看出實例b的constructor屬性就是Base,那麼咱們能夠猜想new是否是至少還作了第4件事:segmentfault

javascriptb.constructor = Base;

以上結果看似正確,下面咱們進行一點修改,這裏咱們修改掉原型的constructor屬性:app

javascriptBase.prototype.constructor = function Other(){ };
var b = new Base();
console.dir(b.constructor); // [Function: Other]

狀況就不同了,能夠看出,以前的猜想是錯誤的,第4件事應該是這樣的:函數

javascriptb.constructor = Base.prototype.constructor;

這裏犯了一個錯誤,那就是沒有理解好這個constructor的實質:當咱們建立一個函數時,會自動生成對應的原型,這個原型包含一個constructor屬性,使用new構造的實例,能夠經過原型鏈查找到constructor。以下圖所示:this

constructor

這裏很是感謝zonxin同窗指出個人錯誤。spa

若是構造函數有返回值呢?

通常狀況下構造函數沒有返回值,可是咱們依舊能夠獲得該對象的實例;若是構造函數有返回值,憑直覺來講狀況應該會不同。咱們對於以前的構造函數進行一點點修改:

javascriptfunction Base() {
    this.str = "aa";
    return 1;
    // return "a";
    // return true;
}
var b = new Base();
console.dir(b); // { str: 'aa'}

咱們在構造函數裏設置的返回值好像沒什麼用,返回的仍是原來對象的實例,換一些例子試試:

javascriptfunction Base() {
    this.str = "aa";
    return [1];
    // return {a:1};
}
var b = new Base();
console.dir(b); // [1] or {a: 1}

此時結果就不同了,從上面的例子能夠看出,若是構造函數返回的是原始值,那麼這個返回值會被忽略,若是返回的是對象,就會覆蓋構造的實例

new至少作了4件事

總結一下,new至少作了4件事:

javascript// new Base();

// 1.建立一個空對象 obj
var obj = {};
// 2.設置obj的__proto__爲原型
obj.__proto__ = Base.prototype;
// 3.使用obj做爲上下文調用Base函數
var ret = Base.call(obj);
// 4.若是構造函數返回的是原始值,那麼這個返回值會被忽略,若是返回的是對象,就會覆蓋構造的實例
if(typeof ret == 'object'){
    return ret;
} else {
    return obj;
}

new的不足

在《Javascript語言精粹》(Javascript: The Good Parts)中,道格拉斯認爲應該避免使用new關鍵字:

If you forget to include the new prefix when calling a constructor function, then this will not be bound to the new object. Sadly, this will be bound to the global object, so instead of augmenting your new object, you will be clobbering global variables. That is really bad. There is no compile warning, and there is no runtime warning.

大意是說在應該使用new的時候若是忘了new關鍵字,會引起一些問題。最重要的問題就是影響了原型查找,原型查找是沿着__proto__進行的,而任何函數都是Function的實例,一旦沒用使用new,你就會發現什麼屬性都查找不到了,由於至關於直接短路了。以下面例子所示,沒有使用new來建立對象的話,就沒法找到原型上的fa1屬性了:

javascriptfunction F(){ }
F.prototype.fa1 = "fa1";

console.log(F.fa1);       // undefined
console.log(new F().fa1); // fa1

這裏我配合一張圖來講明其中原理,黃色的線爲原型鏈,使用new構造的對象能夠正常查找到屬性fa1,沒有使用new則徹底走向了另一條查找路徑:

原型查找

以上的問題對於有繼承的狀況表現得更爲明顯,沿着原型鏈的方法和屬性全都找不到,你能使用的只有短路以後的Function.prototype的屬性和方法了。

固然了,遺忘使用任何關鍵字都會引發一系列的問題。再退一步說,這個問題是徹底能夠避免的:

javascriptfunction foo()
{   
   // 若是忘了使用關鍵字,這一步驟會悄悄幫你修復這個問題
   if ( !(this instanceof foo) )
      return new foo();

   // 構造函數的邏輯繼續……
}

能夠看出new並非一個很好的實踐,道格拉斯將這個問題描述爲:

This indirection was intended to make the language seem more familiar to classically trained programmers, but failed to do that, as we can see from the very low opinion Java programmers have of JavaScript. JavaScript’s constructor pattern did not appeal to the classical crowd. It also obscured JavaScript’s true prototypal nature. As a result, there are very few programmers who know how to use the language effectively.

簡單來講,JavaScript是一種prototypical類型語言,在建立之初,是爲了迎合市場的須要,讓人們以爲它和Java是相似的,才引入了new關鍵字。Javascript本應經過它的Prototypical特性來實現實例化和繼承,但new關鍵字讓它變得不三不四。

再說一點關於constructor的

雖然使用new建立新對象的時候用討論了這個constructor屬性,可是這個屬性彷佛並無什麼用,也許設置這個屬性就是一種習慣,可以讓其餘人直觀理解對象之間的關係。

歡迎光臨小弟博客:Superlin's Blog
個人博客原文:你真的弄明白new了嗎

參考

相關文章
相關標籤/搜索