是否 JavaScript 實際上具有類別 ( class ) 這種東西呢?
直接明確的答案是:"NO"。
JavaScript 是偽裝成 Java 的語言。
當然就會有偽裝的類別 ( class )。
背後的邏輯,就讓我們來一一細數吧。
前面是介紹物件導向的觀念。和 Javascript 處在的位置。
主要概念
類別 Class 是設計圖。
實例 instances 是照著設計圖做出的實體。
類別的實體 ( instances ) 是由建構器 ( constructor ) 所建構,通常與該類別同名。
可以明確的做實體初始化。
假設我們有個類別 ( Kyle 的模擬語言,YDKJS 的作者 )
class CoolGuy {
specialTrick = nothing
coolGuy( trick ) {
specialTrick = trick
}
showOff() {
output( "Here's my trick: ", specialTrick )
}
}
這時候的類別建構器。
Joe = new CoolGuy( "jumping rope" )
Joe.showOff() // Here's my trick: jumping rope
對真正的類別來說,建構器屬於該類別。
在 JS 中是反過來的。「 類別 」屬於建構器。
有子類別 ( child class ) 和父類別 ( parent class )
子類別會繼承自 ( inherits from ) 父類別所有屬性和方法,當作最初的行為。
子類別只要定義完畢,就會與父類別分離,成為不同的類別。
子類別在定義的時候,可以覆寫掉父類別為之設定的初始值。
有些語言會用 super 取代 inherited: 關鍵字。
概念源自於超類別 ( superclass ),是目前的父類別或祖類別 ( ancestor )。
JS 的 super 是 ES6 的規格,用來解決父層子層的關聯。
同樣來自 Kyle 語言的範例
class Vehicle {
engines = 1
ignition() {
output( "Turning on my engine")
}
drive() {
ignition():
output( "Steering and moving forward!" )
}
}
class Car inherits Vehicle {
wheels = 4
drive(){
inherited: drive()
output( "Rolling on all ", wheels, " wheels!" )
}
}
class SpeedBoat inherits Vehicle {
engines = 2
ignition() {
output( "Turing on my ", engines, " engines.")
}
pilot() {
inherited: drive()
output( "Speeding through the water with ease!")
}
}
父類別 載具 ( Vehicle ),有兩個子類別 車 ( Car ) 和 快艇 ( SpeedBoat)
載具定義了引擎 ( engine ),發動 ( ignition ),和駕駛 ( drive )
車子額外定義了輪子 ( wheels ),並覆寫了駕駛
快艇覆寫了引擎,並額外定義操作 ( pilot ) ( 但還是繼承駕駛。) 咦?
剛剛提到的駕駛,在兩個子類別的行為不同的手法,就是多型 ( polymorphism )。
覆寫過的駕駛 和 繼承駕駛的操作。
因為沒有說是繼承自哪一層父類別的方法,這邊定義為相對多型 ( relative polymorphism ),他參考的就是往上一層查看。
對比兩件事
若使用 SpeedBoat 的 pilot(),他繼承的 drive() 參考的 ignition() 是誰?
ANS: SpeedBoat 裡面的。
但如果你在實體化的是 Vehicle,呼叫 drive() 用的就是 Vehicle 的。
所以複寫後的方法,與原始的方法都會保留著,都可以取用。
以上的觀念,是為了與 JS 的 [[prototype]] 機制討論的對比。
最後強調:
子類別和父類別沒有連結,子類別是父類別的拷貝,複製品。
當有一個子類別有兩個父類別。
好處,直接複製兩個父類別的屬性和方法。
壞處,真的用到方法參考時,用的是哪個要另外設定。
JS 沒有提供原生的機制做多重繼承。
(但還是有開發者把 JavaScript 的多重繼承做出來了!)
JS 沒有類別可以實體化,他只有物件。
物件也不會被複製到其他物件。那要怎麼偽裝?
定義 mixin( sourceObj, targetObj )
子類別 targetObj 會複製 父類別 sourceObj 的屬性。
function mixin( sourceObj, targetObj ) {
for ( var key in sourceObj ) {
if ( !(key in targetObj)) {
targerObj[key] = sourceObj[key];
}
}
return targetObj;
}
針對每個在父類別的屬性,判斷是否存在於子類別。
若沒有,就存值和 key。
定義 sourceObj,把先前的 Vehicle 虛擬碼用 JS 寫出來。
var Vehicle = { // 因為是類別 用大寫表示
engines: 1,
ignition: function() {
console.log( "Turning on my engine." );
},
drive: function() {
this.ignition();
console.log( "Steeing and moving forward!" );
}
}
使用 mixin,做出偽裝的繼承。
var Car = mixin( Vehicle, {
wheels: 4,
drive: function() {
vehicle.drive.call( this );
console.log( "Rooling on all " + this.wheels + " wheels!");
}
})
拷貝已經完成。
但嚴格來說,這樣的拷貝,是拷貝參考。
明確的混合,還有兩種形式。
隱含的混合有一個。
明天再說吧。