動態語言和靜態語言有很大的不同,比如在C++中定義類時,并不分配內存,而在動態語言中定義類時,卻會分配內存。
- 比如在JS中定義了一個函數時,將會為該函數創建一個prototype屬性,這個屬性指向該函數的原型對象;JS中萬物皆對象,一個對象要么是函數的實例,要么是原型的實例。
- 比如在Python中定義了一個類時,將會創建一個類型對象(類其實是能夠創建出類實例的對象,類本身也是實例,而且是metaclass元類的實例);Python中所有的東西都是對象,其要么是類的實例,要么是metaclass元類的實例。
原型對象中的屬性被所有實例所共享,這類似于C++中的靜態成員,靜態成員屬于類本身,而不是屬于對象,但是被類的所有實例所共有。
創建一個空函數
function Person() {};
像這樣創建一個空函數,js解析為以下三步:
- 創建一個Object對象(有constructor屬性及[[Prototype]]屬性);
- 創建一個函數(有name、prototype屬性),再通過prototype屬性引用剛才創建的對象;
-
創建變量Person,同時把函數的引用賦值給變量Person
如圖可以表示為
實例化一個對象
我們用上面這個Person函數去實例化一個對象時,js解析又是怎樣呢?比如:
var angela = new Person();
實例化出來的對象,js解析也分為下面三步:
- 新建一個對象并賦值給變量angela:var angela = {};
- 把這個對象的[[Prototype]]屬性指向函數Person的原型對象:angela.[[Prototype]] = Person.prototype
- 調用函數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
- 作用域鏈和原型鏈
作用域鏈用來查找對象
原型鏈用來查找對象的屬性