JavaScript - 原型繼承(Prototypal Inheritance)與原型鏈(Prototype Chain)

物件間的繼承

在 JavaScript 中的繼承與 OOP 的繼承意義不同,從技術上來說,原型繼承更像是實例將方法委託給其原型,模擬 OOP 子類別繼承父類別方法。

構造函式實現

使用 Object.create() 指定一構造函式的 prototype 屬性為另一構造函式 prototype 屬性物件的原型,實現兩構造函式間的原型繼承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
const Dog = function (firstName, birthYear) {
this.firstName = firstName
this.birthYear = birthYear
}

Dog.prototype.calcAge = function () {
console.log(2023 - this.birthYear)
}

const Pet = function (firstName, birthYear, skill) {
// call() 呼叫 Dog 函式
Dog.call(this, firstName, birthYear)
this.skill = skill
}

Pet.prototype = Object.create(Dog.prototype)
Pet.prototype.constructor = Pet

Pet.prototype.introduce = function () {
console.log(`My name is ${this.firstName} and I can ${this.skill}.`)
}

const aben = new Pet("ABen", 2021, "bark at strangers")

console.log(aben) // Pet {firstName: 'ABen', birthYear: 2021, skill: 'bark at strangers'}
aben.introduce() // My name is ABen and I can bark at strangers.
aben.calcAge() // 2

使用 Object.create()Dog.prototype 屬性物件設為 Pet.prototype 屬性物件的原型,使 Pet 的實例 aben 可以使用 Dog 上的 calcAge() 方法,但因為 Object.create() 不會設置新物件的 constructor 屬性,所以 Pet.prototype.constructor 仍然指向 Dog ,只能以 Pet.prototype.constructor = Pet 校正。

原型鏈示意圖 :

構造函式間的原型鏈圖

ES6 Classes 實現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Dog {
constructor(firstName, birthYear) {
this.firstName = firstName
this.birthYear = birthYear
}

calcAge() {
console.log(2023 - this.birthYear)
}
}

class Pet extends Dog {
constructor(firstName, birthYear, skill) {
// super 負責創建子類別的 this
super(firstName, birthYear)
this.skill = skill
}

introduce() {
console.log(`My name is ${this.firstName} and I can ${this.skill}.`)
}
}
const aben = new Pet("ABen", 2021, "bark at strangers")

console.log(aben) // Pet {firstName: 'ABen', birthYear: 2021, skill: 'bark at strangers'}

aben.calcAge() // 2

aben.introduce() // My name is ABen and I can bark at strangers.

使用 extends 擴展類別作為子類別的父類,並於子類別的 constructor() 函式中呼叫 super() 方法,相當於自動執行父類別的 constructor() 函式。

若子類別中只有與父類別相同的屬性,沒有新增自己的屬性,就可以不用定義 constructor() 函式,子類別會自動調用父類別的 constructor()

Object.create( ) 實現

Object.create() 不用在意 prototype 屬性與 constructor()方法,只要創建物件並指定原型,就可以實現物件間的原型繼承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
const DogPrototype = {
init(firstName, birthYear) {
this.firstName = firstName
this.birthYear = birthYear
},

calcAge() {
console.log(2023 - this.birthYear)
},
}

const PetPrototype = Object.create(DogPrototype)
PetPrototype.init = function (firstName, birthYear, skill) {
DogPrototype.init.call(this, firstName, birthYear)
this.skill = skill
}
PetPrototype.introduce = function () {
console.log(`My name is ${this.firstName} and I can ${this.skill}.`)
}

const aben = Object.create(PetPrototype)
aben.init("ABen", 2021, "bark at strangers")

console.log(aben) // {firstName: 'ABen', birthYear: 2021, skill: 'bark at strangers'}

aben.calcAge() // 2

aben.introduce() // My name is ABen and I can bark at strangers.

原型鏈示意圖 :

Object.create() 物件間的原型鏈圖

原型鏈(Prototype Chain)

原型鏈圖 :

原型鏈圖

重點整理 :

  • 實例的隱式原型 __proto__ 屬性等於構造函式的顯式原型 prototype 屬性。
  • 所有函式的 prototype 屬性默認指向一個空物件,但 Object 除外。
  • 所有函式都是 Function 的實例,包括 Function 本身。

參考資料