JavaScript Class (類別) - Shubo 的程式開發筆記
文章推薦指數: 80 %
JavaScript中沒有真正的「類別」實體。
class 宣告出來的本體是「函式」。
換句話說, class 只是宣告函式的一種特別的 ...
你是否還對ES6JavaScriptclass有點陌生呢?其實JavaScriptclass一點也不難!這篇文章將會解釋JavaScriptclass的觀念以及使用方法,包含class和prototype的關係,如何用extends達到繼承(inheritance)效果、constructor及super的寫法,以及如何使用staticmethod/classfunction(靜態方法),一起來看看吧!
目錄
class語法
class只是宣告函式的一種語法
class的靜態方法(StaticMethod)
用extends繼承類別
覆寫(Override)母類別方法
用super覆寫方法
用super覆寫constructor
super在「物件方法」內使用的限制
繼承靜態方法(StaticMethod)
兩種extends幫你自動建立的[[prototype]]關聯
Reference
class語法
如果要建構新物件,傳統的Prototype-based的寫法會需要:
定義constructor
在prototype物件上定義方法
functionUser(name){
this.name=name;
}
User.prototype.sayHi=function(){
console.log(this.name);
}
letuser=newUser('John');
user.sayHi();
改用class語法改寫,我們需要在classbody裡定義:
constructor方法
其他方法
classUser{
constructor(name){
this.name=name;
}
sayHi(){
console.log(this.name);
}
}
其中
sayHi(){
...
}
這種寫法是在class中定義「物件方法」的語法。
class只是宣告函式的一種語法
JavaScript中沒有真正的「類別」實體。
class宣告出來的本體是「函式」。
換句話說,class只是宣告函式的一種特別的語法。
class背後有幾個分解動作,是JavaScriptEngine幫我們完成的:
把classbody裡的constructor()抓出來,指定給User。
把classbody裡的其他方法指定給User.prototype。
也就是說,透過class語法宣告的User,其實是宣告了一個函式User,其prototype屬性上有我們定義在classbody內的方法。
class的靜態方法(StaticMethod)
class裡面可以宣告靜態方法(staticmethod)。
classArticle{
staticcompare(a,b){
returna.date
用super覆寫constructor
利用super關鍵字,在constructor內,呼叫母類別的constructor:
classAnimal{
constructor(name){
this.name=name;
}
}
classRabbitextendsAnimal{
constructor(name,earLength){
super(name);
this.earLength=earLength;
}
}
letrabbit=newRabbit('John',5);
因為母類別已經有this.name=name;的邏輯了,不需要在子類別重寫一次this.name=name;。
直接呼叫super(name);就可以了。
沒必要的話你也可以不寫,會自動幫妳生成預設值:
classRabbitextendsAnimal{}
//幫你生成預設值constructor(...args){super(...args);}
要注意的點:
一定要呼叫super()。
呼叫super要在使用this.earLength=earLength;出現之前。
為什麼有這樣的寫法限制?
理由其實很簡單!
一般沒有繼承的情況下,在constructor裡面會先建立一個物件,然後把this指向這個物件。
相反地,有繼承的情況下,在子類別的constructor裏就不會有建立物件的動作。
為什麼呢?因為建立物件的動作只需要做一次就好了。
所以我們會預期,物件已經在母類別的constructor裏建立了,否則就會在子物件裡重複動作。
所以,我們要在子類別呼叫super(),
在母類別建立好物件,確保執行到this.earLength=earLength;這一行時,this不是空的。
super在「物件方法」內使用的限制
定義在「物件」上的方法,有兩種寫法(注意,是「物件」不是「類別」):
letuser={
sayHi:function(){
alert("Hello");
}
};
//methodshorthandlooksbetter,right?
letuser={
sayHi(){//sameas"sayHi:function()"
alert("Hello");
}
};
舊的寫法,是把方法指定給一種物件的一種「屬性」。
新的寫法,是物件上的一個「物件方法」。
雖然功能看似是一模一樣的,但是其實他們有「這個」微妙的不同!
那就是:
不能在舊的寫法裡使用super。
下面的例子,用舊的寫法呼叫super會有錯誤:
letanimal={
eat:function(){
//...
}
};
letrabbit={
__proto__:animal,
eat:function(){//Resultinerrors
super.eat();
}
};
rabbit.eat();//Errorcallingsuper
原因在HomeObject這篇有解釋。
大意是說,因為繼承機制的需要,物件方法需要知道「這個物件繼承自哪個母類別」,也就是[[Prototype]]。
所以JavaScript的物件方法多了一個隱藏的[[HomeObject]]屬性,可以記住「這個方法屬於哪個物件」。
簡言之,「類別方法」或「物件方法」的[[HomeObject]]屬性,就是物件本身。
知道方法屬於哪個物件,才能知道物件的[[prototype]]是誰,super才能正確被執行。
這是一個後來才加進JavaScript的新機制。
。
。
。
讓我們來看個[[HomeObject]]的例子!
假設有個繼承關係:longEar—>rabbit—>animal,
則各個方法的[[HomeObject]]分別如下:
letanimal={
name:"Animal",
eat(){
console.log(`${this.name}eats!`);//[[HomeObject]]===animal
}
};
letrabbit={
__proto__:animal,
name:'Rabbit',
eat(){
super.eat();//[[HomeObject]]===rabbit
}
};
letlongEar={
__proto__:rabbit,
name:'LongEar',
eat(){
super.eat();//[[HomeObject]]===longEar
}
};
。
。
。
說了這麼多[[HomeObject]],
到底跟兩種語法的不同有什麼關係?
簡單地說,為了和普通函式有所區別,物件方法必須用foo(){...}語法,
這個函式才會被認為是一個「物件方法」,會多一個特別的隱藏屬性[[HomeObject]],這樣super才能正確執行。
所以改成這樣,就沒問題了:
letanimal={
eat:function(){
//...
}
};
letrabbit={
__proto__:animal,
eat(){//OK
super.eat();
}
};
rabbit.eat();//OK
除了這個差別之外,兩種寫法是等義的。
這樣看來,直接全部改用shorthand寫法替代舊的寫法,應該沒有什麼特別的壞處。
結論是:
super關鍵字,只能在「物件方法」中使用。
foo(){...}可以用super,foo:function(){...}不能用super。
。
。
。
另外,「類別」內的「類別方法」寫法就是foo(){...},
所以不會遇到「物件」內寫法的問題。
。
。
。
以下純閒聊,與主題無關,趕時間可跳過🤪
除非用較新的寫法搭配babel。
例如handleClick=()=>{...}這種ArrowFunctionsinClassProperties的寫法,非常有用,可以用來代替正規handleClick(){...}加上constructor內呼叫this.handleClick=this.handleClick.bind(this);的寫法。
不過也有人提出看法,不鼓勵ArrowFunctionsinClassProperties的用法。
在獲得語言完全採納某個feature之前,提前採用babel轉譯出的結果可能和想像有落差,小心踩坑!
題外話,我也好奇babel針對super的case,會做什麼特別的處理?
畢竟對babel來說,物件上的方法foo(){...}和foo:function(){...}兩種寫法並沒有差別,都會被轉換成一樣的舊寫法。
把範例丟進babel,會跳出錯誤:
superisonlyallowedinobjectmethodsandclasses
看來遇到super語法的時候,babel會檢查是否有正確使用objectmethod的寫法,然後才作對應的transpilation,符合新spec的設計。
繼承靜態方法(StaticMethod)
繼承類別的時候,會連靜態方法也一起繼承!
classAnimal{
staticcompare(a,b){//...}
}
classRabbitextendsAnimal{}
letrabbits=[
newRabbit('John'),
newRabbit('Kevin')
];
rabbits.sort(Rabbit.compare);//callsAnimal.compare
這是透過Rabbit.__proto__===Animal達成的。
兩種extends幫你自動建立的[[prototype]]關聯
以下重點!!!
使用extends語法,會自動建立下列兩種prototype的繼承關係:
Rabbit.proto.__proto__===Animal.proto
Rabbit.__proto__===Animal
第一個是為了達成一般方法的繼承,第二個是為了達成靜態方法的繼承。
Reference
http://javascript.info/class
http://javascript.info/class-inheritance
http://javascript.info/object-methods#method-shorthand←如何將ReactNativeAndroidApp打包成apk並發布上架至GooglePlayStore(2020年更新)[筆記]如何正確實作JavaScriptArrayRandomShuffle亂數排序演算法→Shubo的程式開發筆記
延伸文章資訊
- 1ES6 class 關鍵字- JavaScript (JS) 教學Tutorial - Fooish 程式技術
JavaScript ES6 class 關鍵字. 在ES6 中,引入了Class (類別) 這個新的概念(如果寫過C++ 或Java 等傳統語言應該非常熟悉),透過 class 這新的關鍵字,...
- 2你懂JavaScript 嗎?#21 ES6 Class | Summer。桑莫。夏天
關於ES6 Class,我們先再次檢視先前提過的Widget 與Button 範例。 class Widget { constructor(width, height) ...
- 3[筆記] 談談JavaScript ES6中的Classes - PJCHENder
在JavaScript中仍然是使用prototypal inheritance的方法。 class的這種做法在JavaScript實際上只是syntax sugar,也就是說,它只是用來建立物件...
- 4[JS] JavaScript 類別(Class) | PJCHENder 未整理筆記
[JS] JavaScript 類別(Class). function constructor 有hoisting 的情況,所以可以寫再後面但在前面使用;但是class constructor ...
- 5JavaScript Class Fundamentals: Introduction to ES6 Class
A JavaScript class is a blueprint for creating objects. A class encapsulates data and functions t...