JavaScript ES5 的類繼承
Zakas久負(fù)盛名的《JavaScript高級(jí)程序設(shè)計(jì)(第三版)》完成于2012年,當(dāng)時(shí)ES5剛剛成為標(biāo)準(zhǔn)蛾坯,ES6還沒有浮出水面光酣,class
還只是是保留字,由于ES5對(duì)類的支持不夠完善脉课,特別是類的構(gòu)造和繼承救军,Zakas用了整整一章來(lái)解釋,第六章第三部分在討論類繼承時(shí)倘零,提到了6種繼承方式:
- 原型鏈
- 借用構(gòu)造函數(shù)
- 組合繼承
- 原型式繼承
- 寄生式繼承
- 寄生組合式繼承
作者最為推崇的是第三種組合繼承方式唱遭,但是在后續(xù)的章節(jié)中,也提到了這種方式的缺點(diǎn)呈驶,那就是父類的構(gòu)造函數(shù)實(shí)際上被調(diào)用了兩次拷泽,在后續(xù)的寄生組合繼承模式中有一些改進(jìn),用如下函數(shù)消除多余的一次原型副本創(chuàng)建(見原書P172):
function inheritPrototype(subType, superType){
var proto = object(superType.prototype);
proto.constructor = subType;
subType.prototype = proto;
}
這個(gè)函數(shù)創(chuàng)建一個(gè)父類原型副本俐东,將父類的方法傳遞給了子類,但在這個(gè)模式中订晌,雖然省略了創(chuàng)建父類型的副本虏辫,但還是創(chuàng)建了父類原型的一個(gè)副本,能不能連父類型原型副本的構(gòu)造步驟也省略呢锈拨?
在其他面向?qū)ο笳Z(yǔ)言中砌庄,子類重用父類的方法一般是通過(guò)函數(shù)指針的方法實(shí)現(xiàn)的,JavaScript的函數(shù)沒有簽名奕枢,子類不能直接獲得函數(shù)方法娄昆,但是可以顯示地將父類方法賦予給子類,在這個(gè)思路下缝彬,我嘗試修改inheritPrototype函數(shù)萌焰,將父類原型的函數(shù)直接賦予給子類原型,既達(dá)到了重用的目的谷浅,又避免了多余的副本創(chuàng)建扒俯,代碼示例如下:
function inheritPrototype(sub, base) {
for (var propertyName in base.prototype) {
if (typeof base.prototype[propertyName] === 'function') {
sub.prototype[propertyName] = base.prototype[propertyName];
}
}
}
這個(gè)函數(shù)遍歷父類原型中所有的方法奶卓,然后將方法直接賦予給子類原型,這樣子類就可以完全重用父類的功能代碼撼玄,也避免了創(chuàng)建對(duì)象夺姑。
改造之后,不僅達(dá)到了預(yù)期掌猛,還可以使用構(gòu)造函數(shù)來(lái)創(chuàng)建類的實(shí)例盏浙,類的行為和其他的OO語(yǔ)言也沒有區(qū)別,完整代碼如下所示荔茬,在nodejs環(huán)境中運(yùn)行一切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') {
// 組合繼承方式,為了重用父類方法兔院,構(gòu)造了一個(gè)多余的父類
// Worker.prototype = new Person();
// 寄生組合方式殖卑,改造就在此函數(shù)之內(nèi)
inheritPrototype(Worker, Person);
Worker.prototype.sayJob = function() {
printf(this.job);
};
}
}
寫在后面的話
ES6已經(jīng)完全支持class
,構(gòu)造和繼承變得非常簡(jiǎn)單坊萝,和其他OO語(yǔ)言已經(jīng)大同小異了孵稽,不過(guò)考慮到目前還有很多瀏覽器對(duì)ES6的支持不夠完善,ES5的類繼承還會(huì)陪伴我們走過(guò)很長(zhǎng)一段時(shí)間十偶。