[JavaScriptWeird]No.41 了解原型

前言

JavaScript 用了原型繼承,這表示有個叫作原型的概念。

原型 (Prototype)

JavaScript 用了原型繼承,這表示有個叫作原型的概念,讓我們用圖片來解說。

  • 有個物件在記憶體中,給它個名稱 obj
  • 物件具有屬性或方法,也給個名稱 prop1

我們可以使用點運算子取用這屬性,點運算子便會尋找 prop1,找到它的記憶體位置然後回傳。

JavaScript 所有的物件 (包含函式) 都具有原型屬性,

  • 然而這個屬性會參考到另一個物件,稱為 proto
  • 屬性物件 protoobj 的原型
  • proto 是會被 obj 參考到且取用屬性和方法的物件
  • proto 也可以有些屬性,給個名稱 prop2

當需要 prop2 屬性,可以這麼寫「obj.prop2」,點運算子會尋找 prop2 的參考,最後找到 obj ,但是在 obj 上找不到,於是會往原型找,最後在 proto 上找到 prop2 並回傳。

這麼寫會讓人覺得 prop2obj 上,但其實它在物件原型上。

相同的,原型物件也可以指向另一個原型物件。

每個物件可以有自己的原型,可能這個原型會有另一個屬性 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
2
3
4
5
6
7
8
9
10
11
12
var person = {  
firstName: 'Default',
lastName: 'Default',
getFullName: function(){
return this.firstName + ' ' + this.lastName;
}
}

var john = {
firstName: 'John',
lastName: 'Doe'
}

首先建立兩個物件,一個是 person 物件另一個 john 物件,其中 john 沒有 getFullName 方法,我們要透過原型鏈找到 getFullName 方法。

1
2
3
4
// 僅方便理解原型使用,需要使用原型時不可以使用這種方式!!  
john.__proto__ = person;
console.log(john.getFullName());
// John Doe

這樣子做在實例運用上會有很大的問題,但在這邊只是為了好理解原型。

我們透過 __proto__John 的原型指向 person ,並且嘗試呼叫 getFullName 方法,如同先前所述,在原型鏈上找到了 getFullName 方法。

1
console.log(john.firstName); // John

而當取得 firstName 時,為什麼不是「 Default 」?
因為原型鏈的關係,點運算子會優先在 john 物件內找到 firstName,一旦找到了就不會繼續往下找了。

另外同樣的例子,再度新增一個物件。

1
2
3
4
var jane = {  
firstName: 'Jane'
}
jane.__proto__ = person;

一樣的讓 jane__proto__ 指向 person 物件,指向同個記憶體位址。

1
2
console.log(jane.getFullName());  
// Jane Default

如同預期,因為 jane 物件內有 firstName ,所以顯示出 Jane ,而找不到 lastName 所以轉往 jane 的原型鏈下找,最後找到了 lastName 的值。

0%