動態原型組合繼承
JavaScript ES5 的類繼承
Zakas久負盛名的《JavaScript高級程序設計(第三版)》完成于2012年,當時ES5剛剛成為標準,ES6還沒有浮出水面,class
還只是是保留字,由于ES5對類的支持不夠完善,特別是類的構造和繼承,Zakas用了整整一章來解釋,第六章第三部分在討論類繼承時,提到了6種繼承方式:
- 原型鏈
- 借用構造函數
- 組合繼承
- 原型式繼承
- 寄生式繼承
- 寄生組合式繼承
作者最為推崇的是第三種組合繼承方式,但是在后續的章節中,也提到了這種方式的缺點,那就是父類的構造函數實際上被調用了兩次,在后續的寄生組合繼承模式中有一些改進,用如下函數消除多余的一次原型副本創建(見原書P172):
function inheritPrototype(subType, superType){
var proto = object(superType.prototype);
proto.constructor = subType;
subType.prototype = proto;
}
這個函數創建一個父類原型副本,將父類的方法傳遞給了子類,但在這個模式中,雖然省略了創建父類型的副本,但還是創建了父類原型的一個副本,能不能連父類型原型副本的構造步驟也省略呢?
在其他面向對象語言中,子類重用父類的方法一般是通過函數指針的方法實現的,JavaScript的函數沒有簽名,子類不能直接獲得函數方法,但是可以顯示地將父類方法賦予給子類,在這個思路下,我嘗試修改inheritPrototype函數,將父類原型的函數直接賦予給子類原型,既達到了重用的目的,又避免了多余的副本創建,代碼示例如下:
function inheritPrototype(sub, base) {
for (var propertyName in base.prototype) {
if (typeof base.prototype[propertyName] === 'function') {
sub.prototype[propertyName] = base.prototype[propertyName];
}
}
}
這個函數遍歷父類原型中所有的方法,然后將方法直接賦予給子類原型,這樣子類就可以完全重用父類的功能代碼,也避免了創建對象。
改造之后,不僅達到了預期,還可以使用構造函數來創建類的實例,類的行為和其他的OO語言也沒有區別,完整代碼如下所示,在nodejs環境中運行一切Ok。
'use strict';
//===================
main();
//===================
function main() {
var tmp1 = new Person('Dani', 28);
tmp1.sayName();
tmp1.sayAge();
var tmp2 = new Worker('Kayden', 34, 'Actress');
tmp2.sayName();
tmp2.sayAge();
tmp2.sayJob();
var tmp3 = new Worker('Luna', 30, 'Writer');
tmp3.friends.push('Sunny');
tmp3.sayName();
tmp3.sayAge();
tmp3.sayJob();
return 0;
}
function printf(value) {
console.log(value);
}
// sub類繼承base類原型的所有方法
function inheritPrototype(sub, base) {
for (var propertyName in base.prototype) {
if (typeof base.prototype[propertyName] === 'function') {
printf('Subtype inherited a function: ' + propertyName);
sub.prototype[propertyName] = base.prototype[propertyName];
}
}
}
//父類
function Person(name, age) {
this.name = name;
this.age = age;
this.friends = ['Jenna', 'Carrera', 'Tori'];
if (typeof this.sayName !== 'function') {
Person.prototype.sayName = function() {
printf(this.name);
};
Person.prototype.sayAge = function() {
printf(this.age);
};
}
}
// 子類
function Worker(name, age, job) {
Person.call(this, name, age);
this.job = job;
if (typeof this.sayName !== 'function') {
// 組合繼承方式,為了重用父類方法,構造了一個多余的父類
// Worker.prototype = new Person();
// 寄生組合方式,改造就在此函數之內
inheritPrototype(Worker, Person);
Worker.prototype.sayJob = function() {
printf(this.job);
};
}
}
寫在后面的話
ES6已經完全支持class
,構造和繼承變得非常簡單,和其他OO語言已經大同小異了,不過考慮到目前還有很多瀏覽器對ES6的支持不夠完善,ES5的類繼承還會陪伴我們走過很長一段時間。