前言
JavaScript 用了原型繼承,這表示有個叫作原型的概念。
原型 (Prototype)
JavaScript 用了原型繼承,這表示有個叫作原型的概念,讓我們用圖片來解說。
- 有個物件在記憶體中,給它個名稱
obj
- 物件具有屬性或方法,也給個名稱
prop1
我們可以使用點運算子取用這屬性,點運算子便會尋找 prop1,找到它的記憶體位置然後回傳。
JavaScript 所有的物件 (包含函式) 都具有原型屬性,
- 然而這個屬性會參考到另一個物件,稱為
proto
- 屬性物件
proto
是obj
的原型 proto
是會被obj
參考到且取用屬性和方法的物件proto
也可以有些屬性,給個名稱prop2
當需要 prop2
屬性,可以這麼寫「obj.prop2
」,點運算子會尋找 prop2
的參考,最後找到 obj
,但是在 obj
上找不到,於是會往原型找,最後在 proto
上找到 prop2
並回傳。
這麼寫會讓人覺得 prop2
在 obj
上,但其實它在物件原型上。
相同的,原型物件也可以指向另一個原型物件。
每個物件可以有自己的原型,可能這個原型會有另一個屬性 prop3
,如果我們寫「obj.prop3
」那麼點運算子會怎麼尋找 prop3
?
- 點運算子在
obj
上沒有找到prop3
, 所以改找原型proto
物件 proto
上也沒有prop3
,所以又往另一個proto
找- 最後在這個
proto
上找到prop3
並回傳。
這樣看起來像所有的 prop
屬性都在我們的主物件 obj
上,但其實是在一個稱為原型鏈 ( prototype chain )的東西上。
雖然之前也有個東西稱為範圍鏈,但其實沒什麼關連。
- 範圍鏈是尋找取用的變數
- 原型鏈則是與取用屬性或方法有關,透過原型屬性連結,也就是圖片上的
proto
然而這些 proto
是隱藏起來的,不需要這麼寫「obj.proto.proto.prop3
」,因此只需要「obj.prop3
」, JavaScript 會搜尋原型鏈找出 prop3
。
如果有另一個物件 obj2
,它也可以指向同樣的原型。
所以如果需要的話,物件可以共享一樣的原型記憶體位址。
就是說如果「obj2.prop2
」會與「obj.prop2
」一樣得到同樣的結果。
了解原型與原型鏈的基礎觀念後,我們透過簡單的實作來驗證。
1 | var person = { |
首先建立兩個物件,一個是 person
物件另一個 john
物件,其中 john
沒有 getFullName
方法,我們要透過原型鏈找到 getFullName
方法。
1 | // 僅方便理解原型使用,需要使用原型時不可以使用這種方式!! |
這樣子做在實例運用上會有很大的問題,但在這邊只是為了好理解原型。
我們透過 __proto__
將 John
的原型指向 person
,並且嘗試呼叫 getFullName
方法,如同先前所述,在原型鏈上找到了 getFullName
方法。
1 | console.log(john.firstName); // John |
而當取得 firstName
時,為什麼不是「 Default
」?
因為原型鏈的關係,點運算子會優先在 john
物件內找到 firstName,一旦找到了就不會繼續往下找了。
另外同樣的例子,再度新增一個物件。
1 | var jane = { |
一樣的讓 jane
的 __proto__
指向 person
物件,指向同個記憶體位址。
1 | console.log(jane.getFullName()); |
如同預期,因為 jane
物件內有 firstName
,所以顯示出 Jane
,而找不到 lastName
所以轉往 jane
的原型鏈下找,最後找到了 lastName
的值。