JS 的原型繼承(方法3)-ES6 Class

前言

在 ES6中的 Class(類別)語法,並不是向其他語言真的是以類別為基礎 ( class-based ) 的物件導向,在骨子裡仍然是以原型為基礎 ( prototype-based ) 的物件導向,它只是個語法糖 ( syntactical sugar )。加入 Class(類別)語法的目的,並不是要建立另一套物件導向的繼承模型,而是為了提供更簡潔的語法來作物件建立與繼承,當然,一部份的原因是,讓已經熟悉以類別為基礎的物件導向程式語言的開發者使用,以此提供另一種在物件導向語法上的選擇。

語法糖(syntactical sugar):指的是在程式語言中添加的某些語法,這些語法對語言本身的功能並沒有影響,但是能更方便使用,可以讓程式碼更加簡潔,有更高可讀性。


圖片來源


類別宣告 class declaration

如何使用class

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person{
constructor(firstname, lastname){
this.firstname = firstname;
this.lastname = lastname;
}

getFullName(){
return "Hello "+ this.firstname + " " + this.lastname;
}
}

var john = new Person("John","Doe");
console.log(john);

先設定一個 class類別 Person,裏頭開分為兩塊來看,constructor可以想像成之前所學的函數建構子,若要新增方法 getFullName寫在class內即可。

函數建構子的方式,寫一次相同的程式,會更好理解。

1
2
3
4
5
6
7
8
9
10
11
function Person(firstname, lastname) {
this.firstname= firstname
this.lastname= lastname
}

Person.prototype.getFullName = function(){
return "Hello "+ this.firstname + " " + this.lastname;
}

var john = new Person('John', 'Doe');
console.log(john);


在其他的語言,類別是創建物件的藍圖 blue print,但在ES6,類別本身就是被建立出來的物件,上例 class Person 是一個實際被建立的物件,開發者是從物件class Person建立新物件 john出來的,也就是說,ES6 的 Class,本質仍然是原型繼承,而非變成古典繼承

class的命名規範和先前的函數建構子一樣首字建議大寫。


繼承 Extends

由剛剛的例子做延伸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Person{
constructor(firstname, lastname){
this.firstname = firstname;
this.lastname = lastname;
}

getFullName(){
return "Hello "+ this.firstname + " " + this.lastname;
}
}

class InformalPeron extends Person{
greet() {
return 'Yo' + firstname
}

var john = new InformalPeron('John', 'Doe')
}

結果是

john的原型指向(繼承)了InformalPeron,而InformalPeron也繼承了 Person 的屬性及方法,形成一個原型鏈 prototypal chain


呼叫父類別 Super

extends關鍵字可以作類別的繼承,而在建構式中會多呼叫一個super()方法,用於執行上層父母類別的建構式之用。super也可以用於指向上層父母類別,呼叫其中的方法或存取屬性

繼承時還有有幾個注意的事項:

  • 繼承的子類別中的建構式,super()需要放在建構式第一行,這是標準的呼叫方式。如果有需要傳入參數可以傳入。
  • 繼承的子類別中的屬性與方法,都會覆蓋掉原有的在父母類別中的同名稱屬性或方法,要區為不同的屬性或方法要用super關鍵字來存取父母類別中的屬性或方法,例如super.toString()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}

class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y)
this.color = color
}
toString() {
return super.toString() + ' in ' + this.color
}
}

輸出結果


創建 static 函數

關鍵字 static 定義了一個類別的靜態方法,靜態方法不需要實體化它所屬類別的實例就可以被呼叫,它也無法被已實體化的類別物件呼叫。靜態方法經常被用來建立給應用程式使用的工具函數,可以有效避免全域的污染。

1
2
3
4
5
6
7
8
9
class Point {
constructor(x, y) {
this.x = x
this.y = y
}
static staticFn() {
return 'This is static function'
}
}

全域 namespace 呼叫

1
Point.staticFn  // output: 'This is static function'

創建 setter、getter 函數

getter 及 setter 函數都是 callback function, callback 函數有三個的特點

  1. 由你定義的
  2. 你並沒有調用
  3. 但最終他執行了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Square {
constructor(width, height) {
this.width = width
this.height = this.width
}
get area() {
return this.width + this.height
}
set area(value) {
console.log('value: ' + value)
this.newData = 'Hello!'
this.width = value
this.height = this.width
}
}

const square1 = new Square(5)

getter 在當你讀取此屬性值時,自動調用 (就像 Vue 框架的計算屬性 computed)

1
square1.area  // output: 10

setter 調用於 getter 參數被修改時,並且接受一個 param,值為 setter 被賦予的新值

1
square1.area = 10

在執行 square1.area = 10 後, setter 已經被調用了,現在試著印出 setter 函式內所更改和新增的屬性

1
2
console.log(newData)  // output: 'Hello!'
console.log(width) // output: 10

Getter 及 Setter Codepen DEMO


ES6 class 原型繼承完整 DEMO

小結

類別的語法目前雖然內容少而簡單,但它仍然有不少的好處。如果你把它當作是一種自訂物件類型的簡寫語法來看,它隱藏了很多JS中物件在作初始化與繼承的複雜語法,提供了較為簡單、閱讀性高、較容易維護的語法。並不是說直接使用原型語法來寫達不到,只是原型物件導向的特性需要理解到一定的程度,才有辦法寫出像類別這麼簡短的語法能達到的事情。


資料來源