iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 27
0

是否 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 ),他參考的就是往上一層查看。


對比兩件事

  1. Vehicle 裡的 drive() 有使用 ignition()
  2. SpeedBoat 裡的 pilot() 繼承了 drive()。

若使用 SpeedBoat 的 pilot(),他繼承的 drive() 參考的 ignition() 是誰?

ANS: SpeedBoat 裡面的。
但如果你在實體化的是 Vehicle,呼叫 drive() 用的就是 Vehicle 的。

所以複寫後的方法,與原始的方法都會保留著,都可以取用。

以上的觀念,是為了與 JS 的 [[prototype]] 機制討論的對比。

最後強調:
子類別和父類別沒有連結,子類別是父類別的拷貝,複製品。

多重繼承

當有一個子類別有兩個父類別。
好處,直接複製兩個父類別的屬性和方法。
壞處,真的用到方法參考時,用的是哪個要另外設定。

JS 沒有提供原生的機制做多重繼承。
(但還是有開發者把 JavaScript 的多重繼承做出來了!)

Mixins

JS 沒有類別可以實體化,他只有物件。

物件也不會被複製到其他物件。那要怎麼偽裝?

明確的 Mixins

定義 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!");
    }
})

拷貝已經完成。
但嚴格來說,這樣的拷貝,是拷貝參考。

  1. Vehicle 有引擎,Car 沒有 => 拷貝,Car engine: 1
  2. Vehicle 有點火,Car 沒有 => 拷貝,Car ignition: function (){..}
  3. Vehicle 有開車,Car 也有 => 沒事發生。就用 Car drive: function(){..}
  4. Vehicle 沒輪子,Car 有 => Vehicle 遍歷不到。沒事。

接下來

明確的混合,還有兩種形式。
隱含的混合有一個。

明天再說吧。

參考資料

你所不知道的JS


上一篇
Day26 - 物件的屬性描述器
下一篇
Day28 - 用 JS 做多重繼承?
系列文
你為什麼不問問神奇 JavaScript 呢?30

尚未有邦友留言

立即登入留言