類式繼承
//聲明父類
//聲明父類
function SuperClass() {
this.superValue = true;
}
//為父類添加共有方法
SuperClass.prototype.getSuperValue = function () {
return this.superValue;
};
?
//聲明子類
function SubClass() {
this.subValue = false;
}
?
//繼承父類
SubClass.prototype = new SuperClass();
//為子類添加共有方法
SubClass.prototype.getSubValue = function () {
return this.subValue;
};
?
var instance = new SubClass();
console.log(instance.getSuperValue()); //true
console.log(instance.getSubValue()); //false
類式繼承需要將父類的實(shí)例賦值給子類原型, subClass.prototype 繼承了 superClass。 這種類式繼承有兩個缺點(diǎn)。其一,由于子類通過原型 prototype 對父類實(shí)例化,繼承了父類,父類中的共有屬性要是引用類型,就會在子類中被所有實(shí)例共用,因此一個子類的實(shí)例更改子類原型從父類構(gòu)造函數(shù)中繼承來的共有屬性就會直接影響到其他子類,如下:
function SuperClass() {
this.courses = ['語文', '數(shù)學(xué)', '英語']
}
function SubClass() {}
SubClass.prototype = new SuperClass();
?
var instance1 = new SubClass()
var instance2 = new SubClass()
?
console.log(instance2.courses) //['語文', '數(shù)學(xué)', '英語']
instance1.courses.push('化學(xué)')
console.log(instance2.courses) //['語文', '數(shù)學(xué)', '英語', '化學(xué)']
instance1 的修改直接影響了 instance2,這是一個災(zāi)難的陷阱。其二,由于子類實(shí)現(xiàn)的繼承是靠其原型 prototype 對父類的實(shí)例化實(shí)現(xiàn)的,因此在創(chuàng)建父類的時候,是無法向父類傳遞參數(shù)的,因而在實(shí)例化父類的時候也無法對父類構(gòu)造函數(shù)內(nèi)的屬性進(jìn)行初始化。如何解決這個問題?請繼續(xù)往下看。
構(gòu)造函數(shù)繼承
function SuperClass(current) {
this.courses = ["語文", "數(shù)學(xué)", "英語"];
this.current = current;
}
?
//父類聲明原型方法
SuperClass.prototype.getCourses= function () {
console.log(this.courses);
};
?
//聲明子類
function SubClass(current) {
SuperClass.call(this, current);
}
?
var instance1 = new SubClass("語文");
var instance2 = new SubClass("數(shù)學(xué)");
?
instance1.courses.push('化學(xué)')
console.log(instance1.courses); //["語文", "數(shù)學(xué)", "英語", "化學(xué)"]
console.log(instance1.current); //語文
console.log(instance2.courses); //["語文", "數(shù)學(xué)", "英語"]
console.log(instance2.current); //數(shù)學(xué)
?
instance1.getCourses() //TypeError: instance1.getCourses is not a function
SuperClass.call(this, current) 這條語句是構(gòu)造函數(shù)繼承的精華。由于 call 這個方法可以更改函數(shù)的作用環(huán)境,因此在子類中,對 SuperClass 調(diào)用這個 call 就是將子類中變量在父類中執(zhí)行一遍,由于父類中是給 this 綁定屬性的,因此子類也就繼承了父類的共有屬性。 由于這種類型的繼承沒有涉及原型 prototype,所以父類的的原型方法不會被子類繼承,要想被子類繼承,只能將 showCourse 放在父類構(gòu)造函數(shù)中,但是這樣就違背了代碼復(fù)用的原則。為了綜合以上兩種繼承的優(yōu)點(diǎn),于是有了組合繼承。
組合繼承
//組合繼承
?
function SuperClass(current) {
//引用類型共有屬性
this.courses = ["語文", "數(shù)學(xué)", "英語"];
// 值類型共有屬性
this.current = current;
}
?
SuperClass.prototype.getCourses = function () {
console.log(this.courses);
};
?
SuperClass.prototype.getCurrent = function () {
console.log(this.current);
};
?
// 聲明子類
function SubClass(current, time) {
//構(gòu)造函數(shù)繼承父類屬性
SuperClass.call(this, current);
this.time = time;
}
//類式繼承 子類原型繼承父類
SubClass.prototype = new SuperClass();
//子類原型方法
SubClass.prototype.getTime = function () {
console.log(this.time);
};
在子類構(gòu)造函數(shù)中執(zhí)行父類構(gòu)造函數(shù),在子類的原型上實(shí)例化父類就是組合模式,子類實(shí)例中更改父類繼承下來的引用類型屬性 courses 不會改變其他實(shí)例,測試如下
var instance1 = new SubClass("語文", "9:00");
instance1.getTime(); //9:00
instance1.courses.push('化學(xué)')
instance1.getCourses(); //["語文", "數(shù)學(xué)", "英語", "化學(xué)"]
instance1.getCurrent(); //語文
console.log(instance1.current)//語文
?
var instance2 = new SubClass("數(shù)學(xué)", "10:00");
instance2.getTime(); //10:00
instance2.getCourses(); //["語文", "數(shù)學(xué)", "英語"]
instance2.getCurrent(); //數(shù)學(xué)
console.log(instance2.current)//數(shù)學(xué)
但是該模式在執(zhí)行子類構(gòu)造函數(shù)時執(zhí)行了一遍父類函數(shù),在實(shí)現(xiàn)子類原型繼承時又執(zhí)行了一遍父類構(gòu)造函數(shù),調(diào)用了父類構(gòu)造函數(shù)兩遍,顯然是一個設(shè)計(jì)缺陷,那還有更好的方式嗎?針對該缺陷,出現(xiàn)了“寄生組合式繼承”
寄生組合式繼承
在介紹這種繼承方式之前,需要先了解 “原型式繼承”和“寄生式繼承”
基礎(chǔ)了解
原型式繼承
function inheritObject(o) {
function F() {}
F.prototype = o;
return new F();
}
?
var course = {
name: "語文",
alikeCourse: ["數(shù)學(xué)", "英語"],
};
?
var newCourse = inheritObject(course);
newCourse.name = "化學(xué)";
newCourse.alikeCourse.push("物理");
?
var otherCourse = inheritObject(course);
otherCourse.name = "政治";
otherCourse.alikeCourse.push("歷史");
?
console.log(newCourse.name); //化學(xué)
console.log(newCourse.alikeCourse); //["數(shù)學(xué)", "英語", "物理", "歷史"]
?
console.log(otherCourse.name); //政治
console.log(otherCourse.alikeCourse); //["數(shù)學(xué)", "英語", "物理", "歷史"]
?
console.log(course.name); //語文
console.log(course.alikeCourse); //["數(shù)學(xué)", "英語", "物理", "歷史"]
inheritObject 可以看做是對類式繼承的一種封裝,其中的過度類 F 相當(dāng)于類式繼承中的子類。在類式繼承中存在的共用引用類型的問題依然存在,但是過度類 F 構(gòu)造函數(shù)中無內(nèi)容,所以開銷較小。
寄生式繼承
“寄生式繼承”是在“原型式繼承”的基礎(chǔ)上繼續(xù)增強(qiáng)。
function inheritObject(o) {
function F() {}
F.prototype = o;
return new F();
}
?
var course = {
name: "語文",
alikeCourse: ["數(shù)學(xué)", "英語"],
};
?
function createCourse(obj) {
//通過原型繼承方式創(chuàng)建新對象
var o = new inheritObject(obj);
// 拓展新對象
o.getName = function () {
console.log(this.name);
};
return o;
}
?
const newCourse = createCourse(course)
這種方式在某個對象內(nèi)部持續(xù)增長屬性,像寄生式生長,所以稱之為寄生繼承。寄生繼承是對原型繼承的二次封裝,并且在二次封裝的過程中對繼承的對象做了拓展,這樣新創(chuàng)建的對象不僅僅有父類中的屬性和方法,而且還添加了新的屬性和方法。在這種思想的基礎(chǔ)上,結(jié)合組合式繼承,衍生了 “寄生組合式繼承”
實(shí)現(xiàn)
function inheritObject(o) {
function F() {}
F.prototype = o;
return new F();
}
function inheritPrototype(subClass, superClass) {
//復(fù)制一份父類的原型副本保存在變量中
var p = inheritObject(superClass.prototype)
//修正因?yàn)橹貙懽宇愒蛯?dǎo)致子類的constructor屬性被修改
p.constructor = subClass
//設(shè)置子類的原型
subClass.prototype = p
}
以上父類原型保存一個副本,賦值給子類原型,從而實(shí)現(xiàn)繼承,且并未重新調(diào)用一次父類函數(shù),測試如下,與組合繼承模式相近
//test
?
function SuperClass(current) {
//引用類型共有屬性
this.courses = ["語文", "數(shù)學(xué)", "英語"];
// 值類型共有屬性
this.current = current;
}
?
SuperClass.prototype.getCourses = function () {
console.log(this.courses);
};
?
SuperClass.prototype.getCurrent = function () {
console.log(this.current);
};
?
// 聲明子類
function SubClass(current, time) {
//構(gòu)造函數(shù)繼承父類屬性
SuperClass.call(this, current);
this.time = time;
}
?
//寄生式繼承 子類原型繼承父類
inheritPrototype(SubClass, SuperClass);
?
//類式繼承 子類原型繼承父類
// SubClass.prototype = new SuperClass();
?
//子類原型方法
SubClass.prototype.getTime = function () {
console.log(this.time);
};
?
var instance1 = new SubClass("語文", "9:00");
var instance2 = new SubClass("數(shù)學(xué)", "10:00");
?
instance1.getTime(); //9:00
instance1.courses.push("化學(xué)");
instance1.getCourses(); //["語文", "數(shù)學(xué)", "英語", "化學(xué)"]
instance1.getCurrent(); //語文
console.log(instance1.current); //語文
?
instance2.getTime(); //10:00
instance2.getCourses(); //["語文", "數(shù)學(xué)", "英語"]
instance2.getCurrent(); //數(shù)學(xué)
console.log(instance2.current); //數(shù)學(xué)
區(qū)別僅在
//寄生式繼承 子類原型繼承父類
inheritPrototype(SubClass, SuperClass);
?
//類式繼承 子類原型繼承父類
// SubClass.prototype = new SuperClass();
從而實(shí)現(xiàn)多個子類多個實(shí)例不相互影響,父類構(gòu)造函數(shù)僅僅調(diào)用一次,堪當(dāng) JavaScript 繼承的終極模式。