一路上感謝各位讀者們的支持和回饋。
本 30 天系列文目前已經將篇幅重新整理、編纂成冊。
《JavaScript 概念三明治》在天瓏書局上架囉!
喜歡這個系列,想閱讀更詳細原理說明的讀者可以參考:
https://www.tenlong.com.tw/products/9789864347575
講完了原型鍊,現在我們知道如何透過建構函式去做到類似類別的效果,也透過設定物件的 prototype
屬性達到物件的繼承效果, ES6 之後,甚至出現了 class
關鍵字,讓我們可以用更物件導向的方式去撰寫 JS。
static
靜態方法super
原本我們必須要透過建構函式來來模擬類別產生物件,但是因為函式子實在太像是函式了,所以很容易被搞混。在 ES6 後出現了 class 宣告的方式,讓相關功能的程式碼整體變得更物件導向且直觀、更好閱讀許多。使用 class 宣告類別的寫法會要使用比較多一點語法,但與建構函式不會相差太多:
建構函式:
function User(name){
this.name = name
}
let user1 = new User(name)
User.prototype.getName = function (){
return this.name
}
class 宣告式
class User{
constructor (name){
this.name = name
}
getName(){
return this.name
}
}
可以看出使用了 class 宣告以後,原本建構函式的內容還是一樣,只是被移動到 constructor 函式內而已。而原本我們要取用 prototype
才能達成方法的共享,現在也只要直接在 class 內直接宣告就可以了( 是不是真的乾淨很多 ),注意在 class
內的方法宣吿方式跟一般物件屬性的宣告不太一樣,那是 ES6 後出現、用來宣告函式屬性的縮寫,且方法與方法之間不需要以逗號相隔。
為什麼前面說使用函式宣告式很容易讓開發者把他跟一般函式搞混呢?因為使用 new 運算子搭配函式來創造實體 ( instance ) 的時候,基本上也是一種函式呼叫,而且就算沒有加上 new
運算子,函式呼叫還是有效, JS 不會有提示**,**因此就算真的寫錯了也不容易找到錯誤。而使用 class
來宣告的時候,則只有在使用 new
呼叫的時候,才會有效。
還記得前面提過,想要用建構函式來達成繼承的話,有幾個步驟我們必須自己進行:
建構函式的繼承:
為了繼承「前代」建構函式的內容,所以我們必須自己在「後代」建構函式內呼叫前代建構函式 :
function Human(race){
this.race = race
}
function User(name,race){
this.name = name
Human.call(this,race)
}
原型物件的繼承
修改「後代」建構函式的原型物件使原本存在其中的 proto,屬性從參考 Object
改為參考到前代物件,然後再把原型物件內的函式建構子指回「後代」建構函式,完成原型鍊的串接:
let User.prototype = Object.create(Human.prototype)
User.prototype.constructor = Human
class
是 ES6 後出現的語法糖,語法糖簡化了整個類別宣告的過程,透過 class
宣告類別,讓這一切複雜的設定都變得簡單許多!我們不需要再去修改原型物件,也能直接完成繼承的效果了。使用 class
來實現繼承,會需要搭配另外一個關鍵字 extends
,步驟如下:
創造要被繼承的類別 Human
:
class Human{
constructor (race){
this.race = race
}
getRace(){
return this.race
}
}
創造後代類別 User
,並搭配 extends
指向 Human
,代表 User 繼承 Human
:
class User extends Human{
constructor (name, race) {
// invoke our parent constructor function.
super(race);
this.name = name
}
}
類別建構子constructor
的內容就是原本建構函式的內容;而還記得前面有提到我們必須自己在「後代」建構函式內呼叫「前代」建構函式嗎?現在也不需要這麼麻煩, constructor
內的 super
函式就代表了 被 extends
的 Human
建構函式,所以我只要直接呼叫 super
就可以了。
使用建構函式,我們可以在原型物件上新增共享的方法,在 class
宣告中當然也做得到,其實就是在 constructor
外定義的方法,其實前面已經有提過了:
class Human{
constructor (race){
this.race = race
}
getRace(){ // will be set on the prototype object
return this.race
}
}
static
靜態方法靜態方法是物件導向裡面的概念。靜態方法只能由類別本身取得的方法,在產生出來的實例 ( instance )中是無法取得的。static
和 class
ㄧ樣是語法糖,使用 static
關鍵字定義的方法,會直接被指派到該 class
上,所以就只能從該類別上直接取得,像是這樣:
class User {
constructor(name){
this.name = name
}
static getUserType (){
return 'technical'
}
}
User.getUsertype() //'techical'
對應前面的建構函式,就有一點像是這樣:
function User (name){
this.name = name
}
User.getUserType = function(){
return 'technical'
}
如果從建構函式來看靜態方法的話可能會稍微有一點奇怪,不過畢竟函式本身也是物件嘛,要在之上新增屬性本來就是合法的。
super
剛剛說到類別建構子與建構函式內容相同,而裡面的 super
又代表了被繼承類別(或稱前代類別),所以在「後代」類別建構子內一定要呼叫 super
才能有效完成屬性繼承,而在 class
內定義的其他方法則會被定義到原型物件內,所以如果想要取得「前代」建構函式原型物件內的函式,可以直接用 super
來取用,以前面 Human
類別為例子,在 User
類別內就可以這樣做:
class User {
constructor(name){
super()
this.name = name
}
getRace(){
return super.getRace()
}
}
在我們了解了 JS 內,原型的運作方式之後,我們利用原型達成了繼承的效果,了解了什麼是原型鍊,之後在今天的這篇文章裡面我們又結合了上述提到的所有知識了解了 class
語法糖的使用方式,還有跟舊版建構函式寫法的對應。儘管一切很複雜,相信讀到這裡的你一定有不少收穫。
雖然快結束了,不過如果你對我寫的系列文有興趣,歡迎訂閱,已經訂閱我的人,也非常感謝你們,你們的閱讀就是我寫下去的最大動力,希望我可以把 30 天都撐完!