JavaScript之深入多種繼承方式

一、前言

上一篇文章講解了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)為寄生組合式繼承是引用類型最理想的繼承范式嗡害。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末焚碌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子霸妹,更是在濱河造成了極大的恐慌十电,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叹螟,死亡現(xiàn)場(chǎng)離奇詭異鹃骂,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)罢绽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門畏线,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人良价,你說我怎么就攤上這事寝殴。” “怎么了明垢?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵蚣常,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我痊银,道長(zhǎng)抵蚊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任溯革,我火速辦了婚禮贞绳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鬓照。我一直安慰自己熔酷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布豺裆。 她就那樣靜靜地躺著拒秘,像睡著了一般号显。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上躺酒,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天押蚤,我揣著相機(jī)與錄音,去河邊找鬼羹应。 笑死揽碘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的园匹。 我是一名探鬼主播雳刺,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼裸违!你這毒婦竟也來了掖桦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤供汛,失蹤者是張志新(化名)和其女友劉穎枪汪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怔昨,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雀久,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趁舀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赖捌。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖赫编,靈堂內(nèi)的尸體忽然破棺而出巡蘸,到底是詐尸還是另有隱情奋隶,我是刑警寧澤擂送,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站唯欣,受9級(jí)特大地震影響嘹吨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜境氢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一蟀拷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧萍聊,春花似錦问芬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)强戴。三九已至,卻和暖如春挡鞍,著一層夾襖步出監(jiān)牢的瞬間骑歹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工墨微, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留道媚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓翘县,卻偏偏與公主長(zhǎng)得像最域,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锈麸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344