昨天講完 Javascript OOP 兩個重要支柱,今天接著這個主題,來講講 class 吧!
Class 可以想像成印章,每壓一下就蓋出一個印,每 new
一下就產生一個物件。
class
是 ECMAScript 6 引入的語法,但由於 Javascript 仍是基於原型(prototype-based)的語言,所以這個所謂的 class
,其實也只是語法糖,Javascript 依然沒有真正的 "class",所以透過原型鏈(prototype chain) 來營造出繼承的效果。
class
可以當作特別形式的函式,所以宣告也有分 class expressions 跟 class declarations,我個人比較喜歡後者,可以少寫一點XD
class expressions
const Person = class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
};
class declarations
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
上述都只是宣告,現在用 new
來真正將這些 class「實體化」:
const person = new Person('Joey', 20);
基本上 class
內的語法,跟我們昨天用 function 在寫的時候其實非常像,只是有幾點需要注意:
constructor
,用來建立和初始化一個類別的物件,裡面基本上就是在做初始化,且一個 class
裡面只能有一個。
而一般的 method 則是像下面這樣,用一般的非箭頭函式,但 function
關鍵字也可以省略:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
return `Hello, ${this.name}~`;
}
}
const person = new Person('Joey', 20);
console.log(person.sayHello());
執行結果
Hello, Joey~
class
有幾個分解動作,是 Javascript 背後幫我們完成的:
class
裡的 constructor
() 抓出來,指定給 Person
class
裡的其他 method 指定給 Person.prototype
你會發現其實用 function 都做得到,只是用 class
會比較有在寫 OOP 的感覺,所以才說 class
是「特別形式」的 function。
比如說我們有個 class
叫做 Animal
:
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 發出聲音!');
}
}
Animal
是個比較大的範圍,動物(基本上)都會發出聲音,如果今天我們需要建立一個貓的類別,貓也是動物的一種,所以動物會有的屬性,貓都會有:
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 發出聲音!');
}
}
class Cat {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 發出聲音!');
}
}
可以看到其實很大量的 code 在重複,有這種大類別包含小類別的狀況,就可以使用 extends
去繼承大類別:
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 發出聲音!');
}
}
class Cat extends Animal {}
const cat = new Cat('Lulu', 5);
cat.speak();
console.log(cat.age);
執行結果
Lulu 發出聲音!
5
但基本上不會只寫一行
class Cat extends Animal {}
因為如果子類別長得跟父類別一模一樣,那好像也沒什麼必要分出來,假如我們希望讓貓的聲音更有區別性,可以去覆寫父類別的 method:
class Animal {
constructor(name, age) {
this.name = name;
this.age = age;
}
speak() {
console.log(this.name + ' 發出聲音!');
}
}
class Cat extends Animal {
speak() {
console.log(this.name + ' 喵~~~~');
}
}
const cat = new Cat('Lulu', 5);
cat.speak();
console.log(cat.age);
執行結果
Lulu 喵~~~~!
5
但你是否有注意到一點,Cat
類別裡面怎麼會沒有 constructor
method?這樣也能跑嗎?
其實是可以的,因為如果用 extends
語法去繼承父類別,而又沒有給 constructor
method 的時候,Javascript 會在背後偷偷幫你補上:
class Cat extends Animal {
// 這一段是自動補上的 start
constructor(...args) {
super(...args);
}
// 這一段是自動補上的 end
speak() {
console.log(this.name + ' 喵~~~~');
}
}
這個 super
是只能夠用在子類別的語法,意思是呼叫父類別的建構子。在 constructor
裡面務必要先執行 super
,this
才會有東西,再開始使用 this.name
、this.age
之類的屬性,不然像這樣死掉哦:
Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
發現 class
講完還有一點時間,我們再來看看,同樣是 method,寫在類別上,跟寫在實體化的物件上,有什麼樣的差別呢?
假如我們一樣是電商系統,要判斷 user
的錢包有沒有辦法負擔商品價格,所以我們有個 checkPriceAffordable
的 method。
方案A,如果寫在類別上是這樣:
class User {
constructor(name, money) {
this.name = name;
this.money = money;
}
checkPriceAffordable(price) {
return this.money >= price;
}
}
const user = new User('Joey', 2000);
if (user.checkPriceAffordable(1000)) {
console.log('可以買! 買起來!!!!');
}
執行結果
可以買! 買起來!!!!
方案 B,寫在實體化出來的物件裡面:
class User {
constructor(name, money) {
this.name = name;
this.money = money;
this.checkPriceAffordable = function(price) {
return this.money >= price;
}
}
}
const user = new User('Joey', 2000);
if (user.checkPriceAffordable(1000)) {
console.log('可以買! 買起來!!!!');
}
執行結果
可以買! 買起來!!!!
差別其實就在於
checkPriceAffordable
new
一次就複製一個
當我們呼叫 user.checkPriceAffordable()
的時候,Javascript 是怎麼找到 checkPriceAffordable
這個 method 呢?
查找的順序就是由內而外,沿著原型鏈往上找,先看 user
物件本人有沒有這個 method,如果沒有就順著 __proto__
往上看 User
這個類別有沒有。
所以理論上好像會是存在實體化物件上,呼叫的效率最高。
但事實上是,Javascript 當然也知道這一點,因此有針對原型鏈查找的效能最佳化,其實花費的時間不會差太多,但可以肯定的卻是,存在實體化物件中,每複製一份就多花一份記憶體來存。
因此原則上還是把共用的 method 直接寫在類別或原型上即可。
Javascript 雖然常被說很鬆散,過度自由,但也是因為如此,要寫 FP 要寫 OOP,都可以自由選擇,class
雖然也是透過 prototype 做出來的語法糖,少了像是 private
、public
這種方便的語法,但有總比沒有好,讓這個語言充滿了非常多可能性!
在喧囂嘈雜的社會裡
或許你我都是
寂靜的克隆體
Class MDN
深入淺出 JavaScript ES6 Class
少了像是 private、public 這種方便的語法
來寫 TypeScript 吧~ (逃)
TypeScript 真的是時代所趨啊!這個時代的年輕人要學的東西愈來愈多了XD