構(gòu)造函數(shù)繼承
類式繼承是在函數(shù)對(duì)象內(nèi)調(diào)用父類的構(gòu)造函數(shù),使得自身獲得父類的方法和屬性。call和apply方法為類式繼承提供了支持。通過改變this的作用環(huán)境,使得子類本身具有父類的各種屬性。
只繼承構(gòu)造函數(shù)的屬性,不繼承原型的屬性。 解決原型鏈缺點(diǎn)??梢岳^承多個(gè)構(gòu)造函數(shù)的屬性,在子實(shí)例中可以向父類傳參。
缺點(diǎn):無法實(shí)現(xiàn)構(gòu)造函數(shù)的復(fù)用;每個(gè)新實(shí)例都有父類構(gòu)造函數(shù)副本,臃腫。
var?father =?function() {
??this.age = 52;
??this.say =?function() {
????alert('hello i am '+?this.name?' and i am '+this.age +?'years old');
??}
}
var?child =?function() {
??this.name =?'bill';
??father.call(this);
}
var?man =?new?child();
man.say();
原型鏈繼承(借用原型鏈實(shí)現(xiàn)繼承)
function Parent1(){ this.name = "parent1"; this.colors = ["red","blue","yellow"]; } function Child1(){ this.name = "child1"; } Child1.prototype = new Parent1();//把子類的prototype設(shè)置為父類的實(shí)例
實(shí)例可繼承的屬性有:實(shí)例構(gòu)造函數(shù)的屬性,父類構(gòu)造函數(shù)屬性,父類原型的屬性,新實(shí)例不會(huì)繼承父類實(shí)例的屬性。
優(yōu)點(diǎn):繼承了父類的模板,又繼承了父類的原型對(duì)象,
缺點(diǎn):1 如果屬性是引用類型的話,會(huì)共享引用類型。
2 新實(shí)例無法向父類構(gòu)造函數(shù)傳參,繼承單一。父類實(shí)例傳參,不是子類實(shí)例化傳參。
組合式繼承
1 可以繼承父類原型上的屬性,可以傳參,可以服用。
2 每個(gè)新實(shí)例引入構(gòu)造函數(shù)屬性上私有的。
缺點(diǎn):調(diào)用兩次父類構(gòu)造函數(shù),耗內(nèi)存。子類的構(gòu)造函數(shù)會(huì)代替原型上的構(gòu)造函數(shù)。
這里所謂的組合是指組合借用構(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),能否解決上述兩個(gè)實(shí)例對(duì)象沒有被隔離的問題呢?
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兩個(gè)實(shí)例對(duì)象已經(jīng)被隔離了。
但這種方式仍有缺點(diǎn)。父類的構(gòu)造函數(shù)被執(zhí)行了兩次,第一次是Child2.prototype = new Parent2(),第二次是在實(shí)例化的時(shí)候,這是沒有必要的。
組合式繼承優(yōu)化1
直接把父類的原型對(duì)象賦給子類的原型對(duì)象
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(s1 instanceof Child3); // true console.log(s1 instanceof Parent3); // true
可以看到,我們無法區(qū)分實(shí)例對(duì)象s1到底是由Child3直接實(shí)例化的還是Parent3直接實(shí)例化的。用instanceof關(guān)鍵字來判斷是否是某個(gè)對(duì)象的實(shí)例就基本無效了。
我們還可以用.constructor來觀察對(duì)象是不是某個(gè)類的實(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)建對(duì)象的方式,它會(huì)創(chuàng)建一個(gè)中間對(duì)象
var p = {name: "p"} var obj = Object.create(p) // Object.create({ name: "p" })
通過這種方式創(chuàng)建對(duì)象,新創(chuàng)建的對(duì)象obj的原型就是p,同時(shí)obj也擁有了屬性name,這個(gè)新創(chuàng)建的中間對(duì)象的原型對(duì)象就是它的參數(shù)。
這種方式解決了上面的所有問題,是繼承的最完美實(shí)現(xiàn)方式。
ES6中繼承
Class?可以通過extends關(guān)鍵字實(shí)現(xiàn)繼承,這比 ES5 的通過修改原型鏈實(shí)現(xiàn)繼承,要清晰和方便很多。
class Parent { } class Child1 extends Parent { 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對(duì)象。
子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。如果子類沒有定義constructor方法,這個(gè)方法會(huì)被默認(rèn)添加,不管有沒有顯式定義,任何一個(gè)子類都有constructor方法。
ES5 的繼承,實(shí)質(zhì)是先創(chuàng)造子類的實(shí)例對(duì)象this,然后再將父類的方法添加到this上面(Parent.apply(this))。
ES6 的繼承機(jī)制完全不同,實(shí)質(zhì)是先創(chuàng)造父類的實(shí)例對(duì)象this(所以必須先調(diào)用super方法),然后再用子類的構(gòu)造函數(shù)修改this。