JavaScript中的類
在相當(dāng)長(zhǎng)的一段時(shí)間里,JavaScript只有一些近似類的語(yǔ)法元素(比如new和instanceof),不過(guò)在后來(lái)的ES6中新增了一些元素,比如class關(guān)鍵字。這是不是以為著JavaScript中實(shí)際上有類呢?簡(jiǎn)單來(lái)說(shuō):不是。
由于類是一種設(shè)計(jì)模式,所以你可以用一些方法近似實(shí)現(xiàn)類的功能。為了滿足對(duì)于類設(shè)計(jì)模式的最普遍需求,JavaScript提供了一些近似類的語(yǔ)法。
構(gòu)造函數(shù)
類實(shí)例是由一個(gè)特殊的類方法構(gòu)造的,這個(gè)方法名通常和類名相同,被稱為構(gòu)造函數(shù)。這個(gè)方法的任務(wù)就是初始化實(shí)例需要的所有信息(狀態(tài))。
類的偽代碼:
class CoolGuy {
specialTrick = nothing
CoolGuy(trick) {
specialTrick = trick
}
showOff() {
output("Here's my trick: ", specialTrick)
}
}
我們可以調(diào)用類構(gòu)造函數(shù)來(lái)生成一個(gè)CoolGuy實(shí)例:
Joe = new CoolGuy( "jumping rope" )
Joe.showOff() // 這是我的絕技:跳繩
類構(gòu)造函數(shù)屬于類,而且通常和類同名。此外,構(gòu)造函數(shù)大多需要用new來(lái)調(diào)用,這樣語(yǔ)言引擎才知道你想要構(gòu)造一個(gè)新的類實(shí)例。
混入
在繼承或者實(shí)例化時(shí),JavaScript的對(duì)象機(jī)制并不會(huì)自動(dòng)執(zhí)行復(fù)制行為。簡(jiǎn)單來(lái)說(shuō),JavaScript中只有對(duì)象,并不存在可以被實(shí)例化的“類”。一個(gè)對(duì)象并不會(huì)被復(fù)制到其他對(duì)象,它們會(huì)被關(guān)聯(lián)起來(lái)。
由于在其他語(yǔ)言中類表現(xiàn)出來(lái)的都是復(fù)制行為,因此JavaScript開(kāi)發(fā)者也想出了一個(gè)方法來(lái)模擬類的復(fù)制行為,這個(gè)方法就是混入。
顯示混入
手動(dòng)實(shí)現(xiàn)復(fù)制功能,在許多庫(kù)和框架中被稱為extend(..),但為了方便理解我們稱之為mixin(..):
// 非常簡(jiǎn)單的mixin(..) 例子:
function mixin(sourceObj, targetObj) {
for (var key in sourceObj) {
// 只會(huì)在不存在的情況下復(fù)制
if (!(key in targetObj)) {
targetObj[key] = sourceObj[key];
}
}
return targetObj;
}
var Vehicle = {
engines: 1,
ignition: function () {
console.log("Turning on my engine.");
},
drive: function () {
this.ignition();
console.log("Steering and moving forward!");
}
};
var Car = mixin(Vehicle, {
wheels: 4,
drive: function () {
Vehicle.drive.call(this);
console.log(
"Rolling on all " + this.wheels + " wheels!"
);
}
});
有一點(diǎn)需要注意,我們處理的已經(jīng)不再是類了,因?yàn)樵贘avaScript中不存在類,Vehicle和Car都是對(duì)象,供我們分別進(jìn)行復(fù)制和黏貼。
現(xiàn)在Car中就有了一份Vehicle屬性和函數(shù)的副本了。從技術(shù)角度來(lái)說(shuō),函數(shù)實(shí)際上沒(méi)有被復(fù)制,復(fù)制的是函數(shù)引用。所以,Car中的屬性ignition只是從Vehicle中復(fù)制過(guò)來(lái)的對(duì)于ignition()函數(shù)的引用。相反,屬性engines就是直接從Vehicle中復(fù)制了值1。
Car已經(jīng)有了drive屬性(函數(shù)),所以這個(gè)屬性引用并沒(méi)有被mixin重寫(xiě),從而保留了
Car中定義的同名屬性,實(shí)現(xiàn)了“子類”對(duì)“父類”屬性的重寫(xiě)。
隱式混入
var Something = {
cool: function () {
this.greeting = "Hello World";
this.count = this.count ? this.count + 1 : 1;
}
};
Something.cool();
Something.greeting; // "Hello World"
Something.count; // 1
var Another = {
cool: function () {
// 隱式把Something 混入Another
Something.cool.call(this);
}
};
Another.cool();
Another.greeting; // "Hello World"
Another.count; // 1 (count 不是共享狀態(tài))
通過(guò)在構(gòu)造函數(shù)調(diào)用或者方法調(diào)用中使用Something.cool.call(this),我們實(shí)際上“借用”了函數(shù)Something.cool()并在Another的上下文中調(diào)用了它(通過(guò)this綁定)。最終的結(jié)果是Something.cool()中的賦值操作都會(huì)應(yīng)用在Another對(duì)象上而不是Something對(duì)象上。
雖然這類技術(shù)利用了this的重新綁定功能,但是Something.cool.call(this)仍然無(wú)法變成相對(duì)(并且更靈活的)引用,所以使用時(shí)千萬(wàn)要小心。通常來(lái)說(shuō),盡量避免使用這樣的結(jié)構(gòu),以保證代碼的整潔和可維護(hù)性。