原型物件與原型鏈

IrisIm發表於2019-04-02

講原型物件之前我們先理清Js的幾個概念

物件

可以從三點來理解:

  • 雜湊表
  • 無序屬性的集合
  • 每個物件都是基於一個引用型別建立的。引用型別可以是原生型別(如Js內建的Object/Array/Date/Function/基本包裝型別String、Number、Boolean等),也可以是自定義型別。

例項

  • 引用型別的值是引用型別的一個例項,即物件是某個特定引用型別的例項

綜上兩個概念,問題來了——

  1. 下面的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,這是因為在讀取一個基本值型別的時候,後臺會自動建立一個對應的基本包裝型別的物件。

  1. 函式是不是物件?
function test(){}
test instanceof Object  // true
typeof test // 'function'
複製程式碼

instanceof檢測出來它是Object的例項,所以它是一個物件。進一步看它的具體型別,typeof檢測它的資料型別是引用型別Function,也就是說test也是Function的一個例項?驗證一下:

test instanceof Function  // true
複製程式碼

建構函式

建構函式和普通函式沒什麼不同,真正使一個函式變為建構函式的,其實是我們的new 操作符。

綜上所述,問題來了——

  1. 建構函式是物件嗎?
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;
}
複製程式碼

參考:

github.com/mqyqingfeng…

Javascript高階程式技術(第3版)

相關文章