iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 28
0
自我挑戰組

你為什麼不問問神奇 JavaScript 呢?系列 第 28

Day28 - 用 JS 做多重繼承?

前情提要

回想昨天的程式碼。

var Car = mixin( Vehicle, {
    wheels: 4,
    drive: function() {
        vehicle.drive.call( this );
        console.log( "Rooling on all " + this.wheels + " wheels!");
    }
})

重訪多型

觀察這段 Vehicle.drive.call( this )
因為指定了父層 Vehicle,他不是相對多型的樣貌 inherits: drive()
這是明確的虛擬多型 ( explicit pseudopolymorphism )。

ES6 才有相對多型的機制。在這之前,必須明確的指定是使用Vehicle.drive(),並用上明確繫結。

明確的混合,比較複雜、難以閱讀。增加的成本遠多於好處。
盡可能避免使用明確的混合。

混合複製

上面的方法,直接把 Vehicle 混入了 Car。

混合複製就是,先把 Vehicle 混進 空物件。
再將 Car 混進這個有 Vehicle 的物件。

不一樣的 mixin。

function mixinCopy( sourceObj, targetObj){
    for( var key in sourceObj ) {
        targetObj[key] = sourceObj[key];
    }
    return targerObj;
}

var Car = mixinCopy( Vehicle, {} )
mixinCopy( {
    // 寫入任何你想放入 Car 的屬性。
}, Car )

JavaScript 函式無法真正被複製,這都是複製參考。
如果你修改了 ignition(),那 Vehicle 和 Car 都會被影響。

在不斷混入的情況下,你就可以做到多重繼承。
在往後的章節,有更多的方法可以達成,並避免麻煩。

寄生式繼承 ( Parasitic inheritance )

function Vehicle() {
    this.engines = 1;
}
Vehicle.prototype.ignition = function() {
    console.log( "Turning on my engine." );
}
Vehicle.prototype.drive = function() {
    this.ignition();
    console.log( "Steering and moving forward!" );
}
// Vehicle 物件建置完成

function Car() {
    var car = new Vehicle();
    
    car.wheels = 4;
    
    var vehDrive = car.drive;
    car.drive = function() {
        vehDrive.call( this );
        console.log( "Rolling on all " + this.wheels + " wheels!");
    };
    return car;
} 

var myCar = new Car();

myCar.drive();
// Turning on my engine.
// Steering and moving forward!
// Rolling on all 4 wheels!

單看 Car。

  1. car 取得 this 繫結。寄生在 Vehicle 上。
  2. 新增屬性 wheels 並指定值 4。
  3. 將 car.drive 儲存在 vehDrive,保留待會要用的參考。
  4. 覆寫 car.drive,並引入 vehDrive。
    若不這樣處理,將會也失去在 drive 裡面調用 ignition。

new Car()呼叫時,有幾件事情發生

  1. 有個空物件被創造
  2. new 的 this 繫結
  3. 若沒有回傳值,將會回傳 Car 物件和給定的屬性。

But 他回傳了 car!Car 物件會被丟棄。
所以在呼叫 Car() 時,可以不用帶 new 喔!

隱含的混合

var Something = {
    cool: function() {
        this.greeting = "Hello World";
        this.count = this.count ? this.count : 1; 
    }
};

Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1

var Another = {
    cool: function() {
        Something.cool.call( this )
    }
}
Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1
  1. Another.cool(); 執行了 cool 的值。
  2. 這時候,將執行 14 行,得到 this 指向 Another 物件。
  3. 最後,使用 Another 物件的 greeting 和 count。

但不管怎麼樣,複雜的程式碼,能避免就避免。

參考資料

你所不知道的JS


上一篇
Day27 - 混合的 class 物件
下一篇
Day29 - 原型
系列文
你為什麼不問問神奇 JavaScript 呢?30

尚未有邦友留言

立即登入留言