JavaScript Class (類別) - Shubo 的程式開發筆記

文章推薦指數: 80 %
投票人數:10人

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.datesuper.run(),1000);//OK setTimeout(function(){super.run();},1000);//Error } } 相反地,你不能用function(){...},因為function不是一個類別方法,沒有super。

用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的程式開發筆記



請為這篇文章評分?