前端原型鏈

  1. 大致理解原型的由來
    在Javascript之父 Brendan Eich  在設計JS的開始過程,數據類型都是對象(Object),這一點和Java很相似,所以他考慮到要不要設計‘繼承機制’,本來js設計之初本來就是想設計一個簡單容易學習的腳本語言,本身其實是不需要“繼承機制”的,但是其數據都是對象,必須需要一種機制把對象連接起來,所以他最好還是設計了“繼承”,但是他沒有引入類的概念(當然現在的es6是有類了),因爲一旦引入了類,這就是大大增加了js的難易程度,這違背了設計之初只是爲了設計簡單的腳本語言的初衷。
    Eich仿照c++和Java,都使用了new和在調用類的過程中都使用了constructor(構造函數),所以在js調用new的過程中,後面跟的不是類而是構造函數,如:
    function DOG(name){
      this.name = name;
    }
    對這個構造函數使用new,就會生成一個狗對象的實例。
    
    var dogA = new DOG('大毛');
    
    alert(dogA.name); // 大毛
    
    //注意this的指向,這個就是指實例對象

    但是如此,new對象還是會有缺點,就是創建的實例對象,無法共享屬性和方法,
     

    function DOG(name){
       this.name = name;
       this.species = '犬科';
    }
    
    //然後,生成兩個實例對象:
    
    var dogA = new DOG('大毛');
    var dogB = new DOG('二毛');
    
    //這兩個對象的species屬性是獨立的,修改其中一個,不會影響到另一個。
    
    dogA.species = '貓科';
    alert(dogB.species); // 顯示"犬科",不受dogA的影響
    
    //每一個實例對象,都有自己的屬性和方法的副本。這不僅無法做到數據共享,也是極大的資源浪費。

    考慮到這些,Brendan Eich決定爲構造函數設置一個prototype屬性。這個屬性包含一個對象(以下簡稱"prototype對象"),所有實例對象需要共享的屬性和方法,都放在這個對象裏面;那些不需要共享的屬性和方法,就放在構造函數裏面。實例對象一旦創建,將自動引用prototype對象的屬性和方法。也就是說,實例對象的屬性和方法,分成兩種,一種是本地的,另一種是引用的。如下
     

    function DOG(name){
        this.name = name;
    }
    DOG.prototype = { species : '犬科' };
    var dogA = new DOG('大毛');
    var dogB = new DOG('二毛');
    alert(dogA.species); // 犬科
    alert(dogB.species); // 犬科
    
    //現在,species屬性放在prototype對象裏,是兩個實例對象共享的。只要修改了prototype對象,就會同時影響到兩個實例對象。
    
    DOG.prototype.species = '貓科';
    alert(dogA.species); // 貓科
    alert(dogB.species); // 貓科
    由於所有的實例對象共享同一個prototype對象,那麼從外界看起來,prototype對象就好像是實例對象的原型,而實例對象則好像"繼承"了prototype對象一樣。
  2. 原型之間的關係
    -構造函數的prototype與構建的實例對象的原型是相等的
     
    function Person() {}
    var person = new Person();
    console.log(person.__proto__ === Person.prototype); // true

    構造函數等於其實例對象的原型的構造函數
    function Person() {}
    
    var person = new Person();
    
    console.log(person.__proto__ == Person.prototype) // true
    console.log(Person.prototype.constructor == Person) // true
    console.log(person.constructor === Person);
    // 順便學習一個ES5的方法,可以獲得對象的原型
    console.log(Object.getPrototypeOf(person) === Person.prototype) // true

    當讀取實例對象的屬性時,如果在對象中找不到,就會查找與對象關聯的原型中的屬性,如果還查不到,就去找原型的原型(如何理解這種話呢,就是對象是一直有原型的,而原型也是對象,比如Person.prototype其實就是一個對象,只不過他的原型就是Object.prototype),一直找到最頂層爲止。

  3. 原型鏈
     

    簡單的回顧一下構造函數、原型和實例的關係:每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那麼假如我們讓原型對象等於另一個類型的實例,結果會怎樣?顯然,此時的原型對象將包含一個指向另一個原型的指針,相應地,另一個原型中也包含着一個指向另一個構造函數的指針。假如另一個原型又是另一個類型的實例,那麼上述關係依然成立。如此層層遞進,就構成了實例與原型的鏈條。這就是所謂的原型鏈的基本概念。——摘自《javascript高級程序設計》

    那 Object.prototype 的原型呢?

    console.log(Object.prototype.__proto__ === null) // true

    引用阮一峯老師的 《undefined與null的區別》 就是:

    null 表示“沒有對象”,即該處不應該有值。

    所以 Object.prototype.__proto__ 的值爲 null 跟 Object.prototype 沒有原型,其實表達了一個意思。

    所以查找屬性的時候查到 Object.prototype 就可以停止查找了。

    最後一張關係圖也可以更新爲:

    圖中由相互關聯的原型組成的鏈狀結構就是原型鏈,也就是藍色的這條線。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章