理解JS中的原型

動態語言和靜態語言有很大的不同,比如在C++中定義類時,并不分配內存,而在動態語言中定義類時,卻會分配內存。

  • 比如在JS中定義了一個函數時,將會為該函數創建一個prototype屬性,這個屬性指向該函數的原型對象;JS中萬物皆對象,一個對象要么是函數的實例,要么是原型的實例。
  • 比如在Python中定義了一個類時,將會創建一個類型對象(類其實是能夠創建出類實例的對象,類本身也是實例,而且是metaclass元類的實例);Python中所有的東西都是對象,其要么是類的實例,要么是metaclass元類的實例。

原型對象中的屬性被所有實例所共享,這類似于C++中的靜態成員,靜態成員屬于類本身,而不是屬于對象,但是被類的所有實例所共有。

創建一個空函數

function Person() {};

像這樣創建一個空函數,js解析為以下三步:

  1. 創建一個Object對象(有constructor屬性及[[Prototype]]屬性);
  2. 創建一個函數(有name、prototype屬性),再通過prototype屬性引用剛才創建的對象;
  3. 創建變量Person,同時把函數的引用賦值給變量Person


    如圖可以表示為

實例化一個對象

我們用上面這個Person函數去實例化一個對象時,js解析又是怎樣呢?比如:

var angela = new Person();

實例化出來的對象,js解析也分為下面三步:

  1. 新建一個對象并賦值給變量angela:var angela = {};
  2. 把這個對象的[[Prototype]]屬性指向函數Person的原型對象:angela.[[Prototype]] = Person.prototype
  3. 調用函數Person,同時把this指向剛創建的對象angela,對這個對象進行初始化:Person.apply(angela,arguments)
    如圖可表示為

    總結:構造函數、原型和實例之間的關系,每個構造函數包含一個指向原型對象的指針prototype,原型對象都包含一個指向構造函數的指針constructor,而實例都包含一個指向原型對象的內部指針_proto_(有的地方稱為[[prototype]])。

重寫prototype對象

在上面兩個例子的基礎上,再進行如下操作

Person.prototype = {
    name: "bruce",
    age: 23
};
  • 上面使用的語法將會完全重寫默認的prototype對象,其會直接導致Person的prototype對象里面沒有了constructor屬性,constructor屬性只能從Person.prototype.proto.constructor繼承過來(Person.prototype的原型對象為Object原型對象),即constructor將指向Object構造函數
  • 把原型對象修改為另外一個對象就等于,切斷了構造函數Person和最初的原型對象之間的聯系。
    但是實例angela和最初的原型對象之間的聯系不變。
    注:實例中的_proto_指針僅指向原型,而不指向構造函數。

搜索一個屬性

  • 每當代碼讀取某個對象的屬性時,首先會在對象實例本身中搜索,如果在實例中找到了該屬性,則返回該屬性。如果沒有找到,則繼續在_proto_指向的原型對象中搜索,如果找到了該屬性,則返回該屬性。如此繼續下去。
    也就是說當為對象實例添加了一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。
  • hasOwnProperty方法可以檢測一個屬性是存在于實例中,還是存在于原型中,只有給定屬性存在于實例中,才會返回true。

prototye屬性

  • JS中萬物皆對象,通常來說,javascript中的對象就是一個指向原型對象的指針和一個自身的屬性列表。
    prototype屬性本質上它就是一個普通的指針,其之所以特別,是因為javascript時讀取屬性時的遍歷機制決定的。
    只有構造函數才具有prototype屬性,且只有以下對象才是構造函數:
    Object、Function、Array、Date、String
    javascript創建對象時采用了寫時復制的理念,當調用構造函數創建一個實例后,該實例內部將包含一個指針(內部屬性,下圖中暫時稱為inobj),指向構造函數的原型對象。
    調用構造函數創建一個實例
  • 而普通對象是沒有prototype屬性的,在chrome上對象有一個_proto_屬性指向對象的原型,但是
    在其他瀏覽器上這個屬性對外完全不可見,要取得普通對象的原型對象,可以調用Object.getPrototypeOf(instance),便可取得實例instance的原型。

總結:JS中的每個對象都包含一個指向其原型對象的指針,這個指針在構造函數是prototype屬性,可以直接訪問;在普通對象中是_proto_屬性,不可以直接訪問。

原型鏈

  • 讓一個函數的原型對象的_proto_指針指向一個實例,而這個實例的_proto_指針又指向另外一個實例,如此層層遞進,就構成了實例與原型之間的一條鏈條,這就是所謂原型鏈的概念。
  • 所有引用類型對象的_proto_指針默認都指向Object,所有函數的默認原型的_proto_指針都指向Object。
  • 原型鏈是JS實現繼承的主要方法。
  • 如果實例A的_proto_指針指向實例B的原型對象,可以直接指向,也可以間接指向(即A->C->B),則稱A是B的實例
  • instanceof操作符
A instanceof B
//如果B的原型對象出現在A的原型鏈中,則返回true
  • 作用域鏈和原型鏈
    作用域鏈用來查找對象
    原型鏈用來查找對象的屬性

參考資料

  1. 公司一位大牛關于prototype的總結
  2. 理解js中的原型繼承
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容