一、前言
上一篇文章講解了JavaScript之深入原型與原型鏈亩码,繼承本質(zhì)就是在構(gòu)造函數(shù)和原型上進(jìn)行一系列的操作季率,以達(dá)到子類能訪問到父類的屬性和方法。下面會(huì)介紹多種繼承方式描沟,及每種繼承的優(yōu)缺點(diǎn)飒泻。
二、構(gòu)造函數(shù)繼承
構(gòu)造函數(shù)的繼承使用.call()
或.apply()
方法吏廉,在子類中調(diào)用父類的構(gòu)造函數(shù)泞遗,本質(zhì)上是在子類中引用父類的構(gòu)造函數(shù),初始化父類構(gòu)造函數(shù)席覆。代碼如下
function Person(name) {
this.name = name;
this.show = function() {
console.log(`構(gòu)造函數(shù)中的方法:${this.name}`);
}
}
Person.prototype.showName = function() {
console.log(`原型鏈上的方法: ${this.name}`);
}
function Man(name) {
Person.call(this, name);
}
const m = new Man('chicABoo');
console.log(m instanceof Man); // true
console.log(m instanceof Person); // false
m.show(); // 構(gòu)造函數(shù)中的方法:chicABoo
m.showName(); // typeError: m.showName is not a function
上面的代碼定義了一個(gè)Person構(gòu)造函數(shù)史辙,Person中有屬性name和show方法,Person的原型對(duì)象上定義了showName方法佩伤;構(gòu)造函數(shù)Man中通過.call()的方法聊倔,初始化了父類的構(gòu)造函數(shù),即能Man繼承了父類的構(gòu)造函數(shù)生巡。
優(yōu)點(diǎn):
1耙蔑、子類能成功繼承到父類的構(gòu)造函數(shù);
2孤荣、子類能繼承多個(gè)父類的構(gòu)造函數(shù)甸陌;
3、可以通過call或apply方法向父類傳參盐股;
缺點(diǎn):
1钱豁、只能繼承到父類構(gòu)造函數(shù)的屬性和方法,無法繼承原型鏈上的屬性和方法疯汁;
2寥院、每個(gè)新的實(shí)例都會(huì)創(chuàng)建父類的副本;
3涛目、無法實(shí)現(xiàn)構(gòu)造函數(shù)的復(fù)用秸谢,每次都要調(diào)用凛澎;
三、原型鏈繼承
原型鏈繼承的方式估蹄,通過子類的原型對(duì)象去指向父類的實(shí)例塑煎,這樣能繼承到父類原型鏈上的屬性和方法,代碼如下
function Person() {}
Person.prototype.name = 'chicAboo';
Person.prototype.showName = function() {
console.log(this.name);
};
function Man() {}
Man.prototype = new Person(); // 關(guān)鍵代碼
const p = new Person();
p.age = 35;
const m = new Man();
m.showName(); // chicAboo
console.log(m instanceof Man); // true
console.log(m instanceof Person); // true
console.log(m.age); // undefined
原型鏈繼承臭蚁,通過子類的原型對(duì)象等于父類的實(shí)例最铁,實(shí)現(xiàn)繼承。
優(yōu)點(diǎn):
1垮兑、子類可繼承父類構(gòu)造函數(shù)中的屬性冷尉、原型鏈上的屬性和方法。
缺點(diǎn):
1系枪、子類創(chuàng)建的實(shí)例無法向父類傳參雀哨;
2、父類引用類型的屬性被所有實(shí)例所共享私爷;如下:
function Person() {}
Person.prototype.names = [];
Person.prototype.showName = function() {
console.log(this.names);
};
function Man() {}
Man.prototype = new Person(); // 關(guān)鍵代碼
const m1 = new Man();
m1.names.push('張三');
const m2 = new Man();
m2.showName(); // ['張三']
從上面例子能看到雾棺,在父類Person的原型鏈上是引用類型即時(shí),在子類實(shí)例m1上push一個(gè)值衬浑,m2也能訪問捌浩。
三、組合繼承
原型鏈繼承和構(gòu)造函數(shù)繼承工秩,都存在致命的缺點(diǎn)尸饺,原型鏈繼承不能傳參、原型鏈上屬性為引用類型時(shí)會(huì)被所有的實(shí)例所共享助币;構(gòu)造函數(shù)繼承侵佃,只能繼承到構(gòu)造函數(shù)的屬性和方法,每次創(chuàng)建實(shí)例奠支,父類都重新生成一遍馋辈。為了解決這些問題,將它們組合起來使用倍谜,便解決了這些問題迈螟,同時(shí)組合繼承也是JavaScript最常用的繼承方式。
function Person(name) {
this.name = name;
this.names = [];
}
Person.prototype.showName = function() {
console.log(this.name);
};
function Man(name, age) {
Person.call(this, name);
this.age = age;
}
Man.prototype.showAge = function () {
console.log(this.age);
};
Man.prototype = new Person; // 原型鏈繼承關(guān)鍵代碼
Man.prototype.constructor = Man; // Man的原型對(duì)象的constructor指向Person尔崔,需手動(dòng)指回來
const m1 = new Man('張三', 20);
const m2 = new Man('李四', 25);
m1.names.push(100);
console.log(m2.names); // []
優(yōu)點(diǎn):
1答毫、結(jié)合了前兩種構(gòu)造函數(shù)的優(yōu)點(diǎn),構(gòu)造函數(shù)繼承傳參和原型鏈繼承復(fù)用季春;
2洗搂、每個(gè)實(shí)例引用的構(gòu)造函數(shù)屬性都是私有的;
缺點(diǎn):
1、調(diào)用了兩次父類構(gòu)造函數(shù)耘拇,Person.call(this)和Man.prototype = new Person()
2撵颊、子類上的構(gòu)造函數(shù)會(huì)代替父類原型鏈上的構(gòu)造函數(shù)(這不能算是缺點(diǎn),上章講過惫叛,類中查找屬性和方法時(shí)倡勇,現(xiàn)在構(gòu)造函數(shù)中查找,如果找不到嘉涌,才會(huì)在原型鏈上查找妻熊,直到找到為止)
四、原型式繼承
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
用函數(shù)包裝一個(gè)對(duì)象仑最,返回這個(gè)函數(shù)的調(diào)用扔役。ES5中Object.create的模擬實(shí)現(xiàn),將傳入對(duì)象作為創(chuàng)建對(duì)象的原型警医。
缺點(diǎn):
1亿胸、 引用類型的屬性值,會(huì)被所有的實(shí)例所共享法严,類似原型鏈损敷。如下:
function create(obj) {
function F() {}
F.prototype = obj;
return new F();
}
const obj = {
name: 'chicABoo',
names: ['zs', 'ls']
};
const c1 = create(obj);
const c2 = create(obj);
c1.names.push('ww');
console.log(c2.names); // ['zs', 'ls', 'ww]
五葫笼、寄生式繼承
創(chuàng)建一個(gè)用于繼承過程的封裝函數(shù)深啤,該函數(shù)內(nèi)部以某種形式來做增強(qiáng)函數(shù),返回該對(duì)象路星。
function createObj (obj) {
const clone = Object.create(obj);
clone.show = function () {
console.log('something...');
};
return clone;
}
這種方式類似構(gòu)造函數(shù)繼承溯街,每次創(chuàng)建就會(huì)創(chuàng)建父類的構(gòu)造函數(shù)。
缺點(diǎn):
1洋丐、沒有原型呈昔,無法復(fù)用;
2友绝、每次都會(huì)創(chuàng)建一遍父類的構(gòu)造函數(shù)堤尾;
六、寄生組合式繼承(常用)
寄生組合在繼承修正了組合繼承調(diào)用兩次父類問題迁客,那么如何修正的呢郭宝?先看下組合繼承,如下:
function Person(name) {
this.name = name;
this.names = [];
}
Person.prototype.showName = function() {
console.log(this.name);
};
function Man(name, age) {
Person.call(this, name); // 第二次調(diào)用父類
this.age = age;
}
Man.prototype.showAge = function () {
console.log(this.age);
};
Man.prototype = new Person(); // 第一次調(diào)用父類
Man.prototype.constructor = Man;
const m1 = new Man('張三', 20);
const m2 = new Man('李四', 25);
m1.names.push(100);
console.log(m2.names); // []
從上面的代碼可以看到掷漱,子類共調(diào)用了父類兩次粘室。
第一次調(diào)用是子類的原型對(duì)象指向父類實(shí)例時(shí)
Man.prototype = new Person()
第二次調(diào)用時(shí)子類實(shí)例時(shí),初始化了父類
const m1 = new Man('張三', 20);
// ....
Person.call(this, name);
借鑒寄生式繼承方式卜范,可以避免Man.prototype = new Person()
這一次調(diào)用衔统,封裝一個(gè)函數(shù),創(chuàng)建父類的副本,為副本添加構(gòu)造函數(shù)锦爵,并將副本賦給子類的原型舱殿。如下:
function inheritPrototype(_sub, _super) {
var prototype = Object.create(_super.prototype); // 創(chuàng)建Super對(duì)象原型的副本
prototype.constructor = _sub; // 為創(chuàng)建的副本添加構(gòu)造函數(shù)
_sub.prototype = prototype; // 將新創(chuàng)建的副本賦值給子類的原型
}
完整代碼如下:
function Person(name) {
this.name = name;
}
Person.prototype.showName = function () {
console.log(this.name);
};
function Man (name, age) {
Person.call(this, name);
this.age = age;
}
function inheritPrototype(_sub, _super) {
var prototype = Object.create(_super.prototype); // 創(chuàng)建Super對(duì)象原型的副本
prototype.constructor = _sub; // 為創(chuàng)建的副本添加構(gòu)造函數(shù)
_sub.prototype = prototype; // 將新創(chuàng)建的副本賦值給子類的原型
}
inheritPrototype(Man, Person);
const c1 = new Man('chicABoo', 35);
c1.showName(); // chicABoo
console.log(c1.age); // 35
console.log(c1 instanceof Man); // true
console.log(c1 instanceof Person); // true
借用高程上的話來說:
這種方式的高效率體現(xiàn)它只調(diào)用了一次 Parent 構(gòu)造函數(shù),并且因此避免了在 Parent.prototype 上面創(chuàng)建不必要的棉浸、多余的屬性怀薛。與此同時(shí),原型鏈還能保持不變迷郑;因此枝恋,還能夠正常使用 instanceof 和 isPrototypeOf。開發(fā)人員普遍認(rèn)為寄生組合式繼承是引用類型最理想的繼承范式嗡害。