js基礎之實現(xiàn)繼承的幾種方式

js 實現(xiàn)繼承的方式有:

  1. 原型鏈繼承;
  2. 構(gòu)造函數(shù)繼承;
  3. 組合繼承(原型鏈繼承 + 構(gòu)造函數(shù)繼承)(最常用);
    原型式繼承;
    寄生式繼承;
  4. 寄生組合繼承 (最佳方式)
繼承方式之間的聯(lián)系

原型鏈繼承

function Super () {
    this.name = 'hhh';
    this.arr = ['nb'];
}
function Sub () {}
Sub.prototype = new Super();
let sub1 = new Sub();
let sub2 = new Sub();

sub1.name = 'yyy';
sub1.arr.push('hehe');
console.log(sub1.name, sub1.arr); // 'yyy', ['nb', 'hehe']
console.log(sub2.name, sub2.arr); // 'hhh', ['nb', 'hehe']
  1. 實現(xiàn)的本質(zhì)是重寫原型對象,代之以一個新類型的實例。Sub.prototype=new Super();從而使子類得以繼承父類的屬性和方法。

注意這里sub1.constructor === Super為 true。(Sub.prototype.__proto__ === Super.prototype實例屬性的查找自下往上進行查找的原則)

  1. 優(yōu)缺點:

優(yōu)點:簡單,易于實現(xiàn)。

缺點:

  • 包含引用類型的原型屬性會被所有實例共享;(這也是為什么要在構(gòu)造函數(shù)中定義屬性,而不是在原型對象中定義屬性的原因)。從例子中可以看出實例 sub1 修改 arr 后,也會改變 實例 sub2.arr 的值。
  • 創(chuàng)建子類實例時,無法向父類構(gòu)造函數(shù)傳參。實際上,應該說無法在不影響所有實例對象的情況下,給父類的構(gòu)造函數(shù)傳遞參數(shù)。

構(gòu)造函數(shù)繼承

function Super(name) {
    this.name = name;
    this.arr = ['nb'];
    this.fun = function() {
        // ...
    }
}
function Sub(name) {
    this.hobby = 'hhh';
    Super.call(this, name);
  // Super.call(this); 可傳參 可不傳
}
let sub1 = new Sub('xql');
let sub2 = new Sub('nnn');

sub1.name = 'xxx';
sub1.arr.push('hehe')
console.log(sub1.name, sub1.arr); // xxx, ['nb', 'hehe']
console.log(sub2.name, sub2.arr); // nnn, ['nb']
  1. 實現(xiàn)的本質(zhì):借用父類的構(gòu)造函數(shù)來增強子類實例。等于是把父類的實例屬性復制了一份給子類實例裝上了(完全沒有用到原型)

  2. 優(yōu)缺點:

優(yōu)點:

  • 解決了子類實例共享父類引用屬性的問題;
  • 創(chuàng)建子類實例時,可以向父類構(gòu)造函數(shù)傳參;
  • 可以實現(xiàn)多繼承(call多個父類對象);

缺點:

  • 方法都在構(gòu)造函數(shù)中定義,函數(shù)復用也就無從談起了。每個子類都有父類實例屬性副本。每個子類實例都持有父類的所有函數(shù)方法,太多了就會影響性能,內(nèi)存爆炸。。
  • 只能繼承父類的實例屬性和方法,不能繼承父類原型屬性和方法。
  • 實例并不是父類的實例,只是子類的實例。

組合繼承(原型鏈繼承 + 構(gòu)造函數(shù)繼承)?。?!

function Super(name) {
    // 只在此處聲明基本屬性和引用屬性
    this.name = name;
    this. arr = ['nb'];
}
//  在原型處聲明函數(shù)
Super.prototype.fun1 = function() {}
Super.prototype.fun2 = function() {}
// ...
function Sub(name) {
    this.hobby = 'hhh';
    Super.call(this, name); // 核心 第二次調(diào)用父類構(gòu)造函數(shù)
}
Sub.prototype = new Super(); // 核心  第一次調(diào)用父類構(gòu)造函數(shù)
Sub.prototype.constructor = Sub;  // 需要修復構(gòu)造函數(shù)的指向
Sub.prototype.fun11 = function() {}
let sub1 = new Sub('xql');

console.log(sub1);
console.log(sub1);
  1. 實現(xiàn)的本質(zhì):把實例函數(shù)都放在原型對象上,以實現(xiàn)函數(shù)復用。同時還要保留借用構(gòu)造函數(shù)方式的優(yōu)點。(使用原型鏈實現(xiàn)對原型屬性和方法的繼承(一般不在原型上寫引用屬性),而通過借用構(gòu)造函數(shù)來實現(xiàn)對實例屬性的繼承)。即在原型上定義方法實現(xiàn)了函數(shù)復用,又能保證每個實例都有它自己的屬性。

通過Super.call(this);繼承父類的基本屬性和引用屬性并保留能傳參的優(yōu)點;
通過Sub.prototype = new Super();繼承父類函數(shù),實現(xiàn)函數(shù)復用。

  • 注:組合繼承需要修復構(gòu)造函數(shù)的指向。因為重寫了原型對象Sub.prototype,這里Sub.prototype.constructor便指向了構(gòu)造函數(shù)Super,需要修復!??!
Sub.prototype = new Super(); 
Sub.prototype.constructor = Sub;
  1. 優(yōu)缺點:

優(yōu)點:

  • 函數(shù)可復用;
  • 可以繼承實例屬性/方法,也可以繼承原型屬性/方法;
  • 既是子類的實例,也是父類的實例;
  • 不存在引用屬性共享問題;
  • 可傳參

ps: 修復了上述兩種方式的缺點。

缺點:子類原型上有一份多余的父類實例屬性;因為父類構(gòu)造函數(shù)被調(diào)用了兩次,生成了兩份;而子類實例上的那一份屏蔽了子類原型上的。又是內(nèi)存浪費。

第一次Super.call(this);語句從父類拷貝了一份父類實例屬性給子類作為子類的實例屬性;
第二次Sub.prototype = new Super();創(chuàng)建父類實例作為子類原型,此時這個父類實例就又有了一份實例屬性,但這份會被第一次拷貝來的實例屬性屏蔽掉,所以多余。
即子類原型上的這份父類實例永遠用不到,被子類實例上的父類實例覆蓋掉了。


原型式繼承(對象的淺復制)

function object(obj) {
    function F() {} // 臨時的構(gòu)造函數(shù)
    F.prototype = obj;
    return new F();
}
  • 實現(xiàn)的本質(zhì):借助原型,基于已有的對象創(chuàng)建一個新對象(相當于對這個對象進行一次淺復制),不必創(chuàng)建自定義類型了(構(gòu)造函數(shù)),得到一個‘純潔’的新對象(沒有任何實例屬性,可看下面的例子)。(類似簡化版的原型鏈繼承)

ES5提供了Object.create()函數(shù),內(nèi)部就是原型式繼承,IE9+支持

let person = {
    name: 'xql',
    friends: ['bb', 'jj', 'hh', 'yy', 'nn']
}
let per1 = object(person);
// let per1 = Object.create(person);
let per2 = object(person);
// let per2 = Object.create(person);

per1.name = 'www';
per1.age = '18'; // 增強
per1.friends.push('xxx');
console.log(per1); // {name: "www", age: "18"}
console.log(per2); // {} 純潔的新對象
console.log(per1.name, per1.friends); // www, ["bb", "jj", "hh", "yy", "nn", "xxx"]
console.log(per2.name, per2.friends); // xql, ["bb", "jj", "hh", "yy", "nn", "xxx"]
  1. 優(yōu)缺點:

優(yōu)點: 從已有對象衍生新對象,不需要創(chuàng)建自定義類型(更像是對象復制,而不是繼承。)
缺點:和原型鏈繼承一樣

  • 原型引用屬性會被所有實例共享;
  • 無法實現(xiàn)代碼復用;

寄生式繼承(穿個馬甲)

function getSubObject(obj) {
    let clone = Object.create(obj);
    clone.attr1 = 1;
    clone.attr2 = 2;
    // ...
    return clone;
}
let person = {
    name: 'xql';
    friends: ['nn'];
}
let per1 = getSubObject(person);
console.log(per1); // {attr1: 1, attr2: 2}
  1. 實現(xiàn)的本質(zhì): 給原型式繼承穿了個馬甲而已,看起來比較像繼承。同樣是基于某個對象或某個信息創(chuàng)建一個對象,然后增強對象,最后返回對象。
  2. 優(yōu)缺點:
    同原型式繼承。

ps: 有缺陷的寄生式繼承 + 不完美的組合繼承 = 完美的寄生組合式繼承


寄生組合繼承

組合繼承最大的問題是,無論什么情況下,都會調(diào)用兩次父類構(gòu)造函數(shù):一次是在創(chuàng)建子類原型時(實現(xiàn)方法的繼承);另一次是在子類構(gòu)造函數(shù)內(nèi)部(實現(xiàn)屬性的繼承)。(第二次子類實例調(diào)用構(gòu)造函數(shù),會覆蓋掉第一次子類原型調(diào)用構(gòu)造函數(shù)生成的實例屬性,使子類原型上的父類構(gòu)造函數(shù)執(zhí)行生成的實例屬性永遠排不上用場,占用了多余的內(nèi)存)。

怎么解決呢?

  1. 所謂寄生組合繼承的實現(xiàn)本質(zhì):不必為了指定子類構(gòu)造函數(shù)的原型而調(diào)用父類構(gòu)造函數(shù),我們所需要的無非就是父類構(gòu)造函數(shù)的原型的一個副本而已。為了繼承父類實例的方法(父類構(gòu)造函數(shù)的原型上的方法),使用寄生式繼承來繼承父類構(gòu)造函數(shù)的原型。
/**
* @param  {function} 子類構(gòu)造函數(shù)
* @param  {function} 父類構(gòu)造函數(shù)
*/
function inherit(SubType, SuperType) {
    let proto = Object.create(SuperType.prototype);
  // let proto = object(SuperType.prototype);
  // object函數(shù)見上 原型式繼承
    proto.constructor = SubType;
    SubType.prototype = proto;
}
function Super(name) {
    this.name = name;
    this.friends = ['nnn'];
}
Super.prototype.fun1 = function() {}
Super.prototype.fun2 = function() {}
// ...
function Sub(name) {
    this.hobby = 'hhh';
    Super.call(this, name);
}
inherit(Sub, Super); // 核心
Sub.prototype.fun11 = function() {}
// ...
let sub1 = new Sub('xql');
console.log(sub1);
console.log(sub1)
  1. 優(yōu)缺點:

優(yōu)點:同組合繼承。但解決了組合繼承的缺點,只調(diào)用了一次父類構(gòu)造函數(shù),避免了在子類構(gòu)造函數(shù)的原型上創(chuàng)建不必要的、多余的屬性。
缺點:理論上沒有。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容