借用構(gòu)造函數(shù)繼承
原型鏈?zhǔn)嚼^承(借用原型鏈實(shí)現(xiàn)繼承)
組合式繼承
組合式繼承優(yōu)化1
組合式繼承優(yōu)化2
ES6中繼承
JS面向?qū)ο笾R中升筏,繼承是比較難比較抽象的一塊內(nèi)容黍聂,而且實(shí)現(xiàn)繼承有很多種方法丸逸,每種方法又各有優(yōu)缺點(diǎn)超陆,更加的讓人奔潰谢肾,這需要對面向?qū)ο笾R中的對象、原型钦铺、原型鏈傀蓉、構(gòu)造函數(shù)等基礎(chǔ)知識掌握透徹,否則《JS高程》里第六章繼承也是看不明白的职抡,網(wǎng)上也有N多的文章葬燎,看了這么多對繼承依然不是很明白,所謂懂得不少道理但依然過不好這一生缚甩。
下面我結(jié)合自己的理解谱净,和參考了《JS高程》和網(wǎng)上文章,總結(jié)一下實(shí)現(xiàn)繼承的幾種方法及優(yōu)缺點(diǎn)擅威,這篇文章適合出去面試前速記壕探。
借用構(gòu)造函數(shù)繼承
function Parent0(){
this.name = "parent0";
this.colors = ["red","blue","yellow"];
}
function Child0(){
Parent0.call( this ); // 或apply
this.type = "child0";
}
第6行,在子類(Child0)中執(zhí)行父類(Parent0)的構(gòu)造函數(shù)郊丛,通過這種調(diào)用李请,把父類構(gòu)造函數(shù)的this指向?yàn)樽宇悓?shí)例化對象引用瞧筛,從而導(dǎo)致父類執(zhí)行的時候父類里面的屬性都會被掛載到子類的實(shí)例上去。
new Child0().name; // Parent0
new Child0().colors; // (3) ["red", "blue", "yellow"]
但是通過這種方式导盅,父類原型上的東西是沒法繼承的
Parent0.prototype.sex = "男";
Parent0.prototype.say = function() {
console.log(" Oh,My God! ");
}
new Child0().sex; // undefined
// Uncaught TypeError: (intermediate value).say is not a function
new Child0().say();
缺點(diǎn):Child1無法繼承Parent1的原型對象较幌,并沒有真正的實(shí)現(xiàn)繼承(部分繼承)
原型鏈?zhǔn)嚼^承(借用原型鏈實(shí)現(xiàn)繼承)
function Parent1(){
this.name = "parent1";
this.colors = ["red","blue","yellow"];
}
function Child1(){
this.name = "child1";
}
Child1.prototype = new Parent1();
這種方式能否解決借用構(gòu)造函數(shù)繼承的缺點(diǎn)呢?來看下面代碼白翻,我們依然為父類的原型添加sex屬性和say方法:
Parent1.prototype.sex = "男";
Parent1.prototype.say = function() {
console.log(" Oh,My God! ");
}
new Child1().sex; // ?男
new Child1().say(); // Oh,My God!
這種方式確實(shí)解決了上面借用構(gòu)造函數(shù)繼承方式的缺點(diǎn)乍炉。
但是,這種方式仍有缺點(diǎn)滤馍,我們來看如下代碼:
var s1 = new Child1();
s1.colors.push("black");
var s2 = new Child1();
s1.colors; // (4) ["red", "blue", "yellow", "balck"]
s2.colors; // (4) ["red", "blue", "yellow", "balck"]
我們實(shí)例化了兩個Child1岛琼,在實(shí)例s1中為父類的colors屬性push了一個顏色,但是s2也被跟著改變了巢株。造成這種現(xiàn)象的原因就是原型鏈上中的原型對象它倆是共用的槐瑞。
這不是我們想要的,s1和s2這個兩個對象應(yīng)該是隔離的阁苞,這是這種繼承方式的缺點(diǎn)困檩。
組合式繼承
這里所謂的組合是指組合借用構(gòu)造函數(shù)和原型鏈繼承兩種方式。
function Parent2(){
this.name = "parent2";
this.colors = ["red","blue","yellow"];
}
function Child2(){
Parent2.call(this);
this.type = "child2";
}
Child2.prototype = new Parent2()
注意第6猬错,9行窗看,這種方式結(jié)合了借用構(gòu)造函數(shù)繼承和原型鏈繼承的有點(diǎn),能否解決上述兩個實(shí)例對象沒有被隔離的問題呢倦炒?
var s1 = new Child2();
s1.colors.push("black");
var s2 = new Child2();
s1.colors; // (4) ["red", "blue", "yellow", "balck"]
s2.colors; // (3) ["red", "blue", "yellow"]
可以看到显沈,s2和s1兩個實(shí)例對象已經(jīng)被隔離了。
但這種方式仍有缺點(diǎn)逢唤。父類的構(gòu)造函數(shù)被執(zhí)行了兩次拉讯,第一次是Child2.prototype = new Parent2(),第二次是在實(shí)例化的時候鳖藕,這是沒有必要的魔慷。
組合式繼承優(yōu)化1
直接把父類的原型對象賦給子類的原型對象
function Parent3(){
this.name = "parent3";
this.colors = ["red","blue","yellow"];
}
Parent3.prototype.sex = "男";
Parent3.prototype.say = function(){console.log("Oh, My God!")}
function Child3(){
Parent3.call(this);
this.type = "child3";
}
Child3.prototype = Parent3.prototype;
var s1 = new Child3();
var s2 = new Child3();
console.log(s1, s2);
但是著恩,我們來看如下代碼:
console.log(s1instanceofChild3); // true
console.log(s1instanceofParent3); // true
可以看到院尔,我們無法區(qū)分實(shí)例對象s1到底是由Child3直接實(shí)例化的還是Parent3直接實(shí)例化的。用instanceof關(guān)鍵字來判斷是否是某個對象的實(shí)例就基本無效了喉誊。
我們還可以用.constructor來觀察對象是不是某個類的實(shí)例:
console.log(s1.constructor.name); // Parent3
從這里可以看到邀摆,s1的構(gòu)造函數(shù)居然是父類,而不是子類Child3伍茄,這顯然不是我們想要的栋盹。
組合式繼承優(yōu)化2
這是繼承的最完美方式
function Parent4(){
this.name = "parent4";
this.colors = ["red","blue","yellow"];
}
Parent4.prototype.sex = "男";
Parent4.prototype.say = function(){console.log("Oh, My God!")}
function Child4(){
Parent4.call(this);
this.type = "child4";
}
Child4.prototype = Object.create(Parent4.prototype)敷矫;
Child4.prototype.constructor = Child4;
Object.create是一種創(chuàng)建對象的方式例获,它會創(chuàng)建一個中間對象
var p = {name: "p"}
var obj = Object.create(p)
// Object.create({ name: "p" })
通過這種方式創(chuàng)建對象汉额,新創(chuàng)建的對象obj的原型就是p,同時obj也擁有了屬性name榨汤,這個新創(chuàng)建的中間對象的原型對象就是它的參數(shù)蠕搜。
這種方式解決了上面的所有問題,是繼承的最完美實(shí)現(xiàn)方式件余。
ES6中繼承
Class 可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承讥脐,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承遭居,要清晰和方便很多啼器。
class Parent {
}
class Child1extendsParent {
constructor(x, y, colors) {
super(x, y); // 調(diào)用父類的constructor(x, y)
this.colors = colors;
}
toString() {
return this.colors + ' ' + super.toString(); // 調(diào)用父類的toString()
}
}
上面代碼中,constructor方法和toString方法之中俱萍,都出現(xiàn)了super關(guān)鍵字端壳,它在這里表示父類的構(gòu)造函數(shù),用來新建父類的this對象枪蘑。
子類必須在constructor方法中調(diào)用super方法损谦,否則新建實(shí)例時會報(bào)錯。如果子類沒有定義constructor方法岳颇,這個方法會被默認(rèn)添加照捡,不管有沒有顯式定義,任何一個子類都有constructor方法话侧。
ES5
的繼承栗精,實(shí)質(zhì)是先創(chuàng)造子類的實(shí)例對象this,然后再將父類的方法添加到this上面(Parent.apply(this))瞻鹏。ES6
的繼承機(jī)制完全不同悲立,實(shí)質(zhì)是先創(chuàng)造父類的實(shí)例對象this(所以必須先調(diào)用super方法),然后再用子類的構(gòu)造函數(shù)修改this新博。