原型(prototype)、原型鍊(prototype chain)和繼承(inheritance)

這篇筆記主要說明了 JavaScript 中非常重要的概念,也就是繼承(inheritance)原型(prototype)原型鍊(prototype chain)

繼承 ( inheritance )

1. 定義

繼承即一個物件擁有另一個物件的屬性(property)方法(method)

2. 古典繼承及原型繼承

  • 古典繼承(classical inheritance)
    C#JAVA常用到的物件繼承方式,古典繼承很流行,也解決了很多問題,但樹狀結構物件的互動模式,一但數量增加,很容易產生複雜、龐大的集合。
    而對於古典繼承,講師提到一種很貼切的形容,就像是今天你打開了燈,馬桶卻突然沖水了。
  • 原型繼承(prototypal inheritance)
    JavaScript的物件繼承種類,相對古典繼承簡易、彈性。

原型鍊 ( prototype chain )

由於 JavaScript使用的是 原型繼承 prototypal inheritance,所以必然會包含原型(prototype)的概念。

一個物件裡面除了所給予的屬性值外,另外也包含一個原型物件__proto__


1
obj.prop1

現在有一個物件obj,而這個物件包含屬性(property)稱做prop1,並可以使用obj.prop1來取用這個屬性。


1
obj.prop2

如果要存取obj.prop2 ,JavaScript 引擎會先在 obj 這個物件的屬性裡去尋找有沒有叫作 prop2 的屬性,如果它找不到,這時候它就會再進一步往該物件的原型物件 __proto__ 裡面去尋找。所以,雖然我們輸入 obj.prop2 的時候會得到回傳值不報錯,但實際上這不是 obj 裡面直接的屬性名稱,而是在 obj__proto__ 裡面找到的屬性名稱( 即obj.proto.prop2但我們不需要這樣打)。


1
obj.prop3

同樣的,每個物件都包含一個原型物件__proto__,包含__proto__本身也不例外,所以如果輸入obj.prop3,JavaScript會先在obj裡尋找,找不到會再往obj.__proto__找,若還是找不到,他就會再往obj.__proto__.__proto__繼續找下去,找到最底層為止。


物件往原型物件查找,再往原型物件的原型物件查找,再往原型物件的原(下略)一直找,鏈接下來的這個型態,就稱為Prototypal Chain原型鏈

原型鏈查找與範圍鏈scope chain(外部參照)是不一樣的東西,前者是去物件的原型對象找屬性和方法,後者是往外部執行環境找變數,這兩者不可搞混。


JS的原型繼承特性,不同的物件可以指向同一個原型物件

若今天有個物件obj2,它可以是和obj1有同樣的原型物件__proto__的。

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',
};

創建兩個物件personjohn,他們都有屬性firstname與lastname,只有person有方法getFullName


1
john.__proto__ = person;

將john的原型proto指向person,現在john的原型是person了

警告: 這樣的指向方式是為了讓人更好理解,在現實開發不可以用這樣的方式。


1
console.log(john.getFullName())        //John Doe;

我們可以得到 "John Doe" 的結果。原本在 john 的這個物件中,是沒有 getFullName() 這個方法的,但由於我讓john物件的原型物件__proto__變成了 person 這個物件,所以當 JavaScript 引擎在 john 物件裡面找不到getFullName() 這個方法時,它便會到__proto__裡面去找,最後它找到了,於是它回傳 "John Doe"的結果。


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

我們會得到的是 John 而不是 'Default',因為 JavaScript 引擎在尋找 john.firstName 這個屬性時,在john 這個物件裡就可以找到了,因此它不會在往 __proto__ 裡面找。這也就是剛剛在上面所的原型鍊(prototype chain)的概念, 一旦它在上層的部分找到該屬性或方法時,就不會在往下層的proto去尋找。


在了解了prototype chain這樣的概念後,讓我們接著看下面這段程式碼:

1
2
3
4
5
6
var jane ={
firstName: 'Jane'
}

jane.__proto__ = person;
console.log(jane.getFullName());

現在,你可以理解到會輸出什麼結果嗎?
答案是 "Jane Default"
因為在 jane 這個物件裡只有 firstName 這個屬性,所以當 JavaScript 引擎要尋找 getFullName() 這個方法和 lastName 這個屬性時,它都會去找 __proto__ 裡面,而這裡面找到的就是一開始建立的person 這個物件的內容。


完整範例程式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var person = {
firstName:'Default',
lastName:'Default',
getFullName: function(){
return this.firstName+ ' ' + this.lastName;
}
}


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

john.__proto__ = person;
console.log(john.getFullName()); // John Doe
console.log(john.firstName); // John

var jane ={
firstName: 'Jane'
}

jane.__proto__ = person;
console.log(jane.getFullName());

資料來源