講原型物件之前我們先理清Js的幾個概念
物件
可以從三點來理解:
- 雜湊表
- 無序屬性的集合
- 每個物件都是基於一個引用型別建立的。引用型別可以是原生型別(如Js內建的Object/Array/Date/Function/基本包裝型別String、Number、Boolean等),也可以是自定義型別。
例項
- 引用型別的值是引用型別的一個例項,即物件是某個特定引用型別的例項
綜上兩個概念,問題來了——
- 下面的a是不是一個物件?
var a = '123'
a instanceof Object // false
a instanceof String // false
typeof a // 'string'
複製程式碼
instanceof檢測它既不是Object物件的例項,也不是String物件的例項。所以它並不是一個物件,那它是什麼型別呢?再看typeof,輸出string,其實它只是一個值型別 String。
ps: 為什麼它可以呼叫一些String物件的方法,如
a.concat
,這是因為在讀取一個基本值型別的時候,後臺會自動建立一個對應的基本包裝型別的物件。
- 函式是不是物件?
function test(){}
test instanceof Object // true
typeof test // 'function'
複製程式碼
instanceof檢測出來它是Object的例項,所以它是一個物件。進一步看它的具體型別,typeof檢測它的資料型別是引用型別Function
,也就是說test也是Function的一個例項?驗證一下:
test instanceof Function // true
複製程式碼
建構函式
建構函式和普通函式沒什麼不同,真正使一個函式變為建構函式的,其實是我們的new 操作符。
綜上所述,問題來了——
- 建構函式是物件嗎?
var B = function(a) {this.a = a }
B instanceof Function // true
B instanceof Object // tre
typeof B // 'function'
複製程式碼
其實結合上面的分析,很容易明白,建構函式也是物件。
ps:我們常常
new Array()
,Array其實是一個js內建的建構函式,也就是一個內建物件,也就是Function的例項。結論:Array是Function型別的例項,其他js內建物件同理。
原型物件
每個函式都有一個原型物件,通過這個物件,可以讓所有由該函式建立的例項共享屬性和方法。函式的原型物件有一個自有屬性constructor,這個屬性指向該函式。
建構函式訪問原型物件
在建立一個新函式時,會為該函式建立一個prototype屬性,通過這個屬性我們就可以訪問到該函式的原型物件。假設有一個建構函式Person
,那麼建構函式的原型物件即Person.prototype
,那麼建構函式和原型物件的關係會是這樣:
物件訪問原型物件
既然可以通過函式的prototype屬性獲得原型物件,那當我建立一個Person
物件person
時,想要通過person訪問建構函式的原型物件怎麼辦呢?這個時候就輪到我們常見的一個屬性__proto__
上場了。其實在ECMA-262中並沒有這樣一個屬性,真正存在的是一個叫[[prototype]]的指標。我們之所以可以在瀏覽器環境中使用這個屬性,是因為瀏覽器幫我們做了這樣一個實現。所以建構函式、原型物件、物件的關係會是這樣:
原型鏈
再往下深入,原型物件也是物件,那基於它會不會也有一個像上圖一樣的關係呢?假設Person.prototype是Parent的例項,那麼他們的關係會是這樣:
假設Parent.prototype是Grandpa的例項,那麼他們的關係會是這樣: 這樣一條由__proto__
連線起來的線就是原型鏈。
我們說所有函式的預設原型都是Object的例項,所以我們原型鏈的終點是Object.prototype。
至此應該都理解了原型和原型鏈了。下面問題來了——
1. Array/Function這些內建物件與其建構函式Function的關係?
Array是內建物件,有一個內部指標__proto__
,指向建構函式的原型物件Function.prototype
。所以Array._proto === Function.prototype
。Function同理,Function.__proto__ === Function.prototype
2. instanceof
到底在做什麼?
我們知道instanceof是用來檢測物件是否是建構函式的一個例項。那麼具體它做了些什麼呢?MDN的說法:建構函式的prototype屬性是否出現在物件的原型鏈中的任何位置。其實已經很明白了,簡單實現一下會更直觀:
function myinstanceof(obj, func) {
var prototypeObj = obj.__proto__;
return prototypeObj === func.prototype
|| myinstanceof(prototypeObj, func);
}
複製程式碼
3. new
操作符到底做了什麼?
為什麼new操作符可以讓一個普通函式變為建構函式?new 操作符到底做了什麼?
當new一個物件時,發生了這些事情:
1. 建立一個新物件
2. 將新物件的內部指標__proto__
指向建構函式的原型物件
3. 賦值this
4. 執行建構函式
5. 返回這個新物件
直觀的使用程式碼表達:
function createObj(func) {
var newObj = {};
newObj.__proto__ = func.prototype;
func.apply(newObj, Array.prototype.slice.call(arguments, 1));
return newObj;
}
複製程式碼
參考:
Javascript高階程式技術(第3版)