JavaScript 繼承

JavaScript 繼承機(jī)制的設(shè)計(jì)思想就是杯聚,原型對(duì)象的所有屬性和方法,都能被實(shí)例對(duì)象共享抒痒。所以我們只要改變對(duì)象的原型幌绍,就可以做到繼承。
先設(shè)置一個(gè)統(tǒng)一的父類Person

    // 定義一個(gè)Person類
    function Person(name) {
        // 屬性
        this.name = name || 'Person';
        // 實(shí)例方法
        this.sleep = function () {
            console.log(this.name + '正在偷懶~~~!');
        }
    }
    // 原型方法
    Person.prototype.work = function (job) {
        console.log(this.name + '的工作是:' + job);
    };

1傀广、原型鏈繼承

將父類的實(shí)例作為子類的原型

function Chinese(address) {
        this.address = address || "earth";
    }
   //要在改變?cè)蛯?duì)象之后
    Chinese.prototype.skin = '黃色';
    Chinese.prototype = new Person();
    // Test Code
    let chinese = new Chinese('亞洲中國');
   console.log(chinese.skin);//undefined
    console.log(chinese.name);//Person
    console.log(chinese.address);//亞洲中國
    chinese.work('IT');//Person的工作是:IT
    chinese.sleep();//Person正在偷懶~~~颁独!
    console.log(chinese instanceof Person); //true 
    console.log(chinese instanceof Chinese); //true
    let beijing = new Chinese('北京');
    console.log(beijing.name);//Person

優(yōu)點(diǎn):

  1. 非常純粹的繼承關(guān)系,實(shí)例是子類的實(shí)例伪冰,也是父類的實(shí)例誓酒;
  2. 父類新增原型方法/原型屬性,子類都能訪問到糜值;
  3. 簡單丰捷,易于實(shí)現(xiàn)。

缺點(diǎn):

  1. 要想為子類新增原型屬性和方法寂汇,得注意一下要在改變?cè)蛯?duì)象之后病往,如上面代碼的skin原型屬性,(算不上缺點(diǎn)骄瓣,注意點(diǎn))停巷;
  2. 無法實(shí)現(xiàn)多繼承;
  3. 來自原型對(duì)象的所有屬性被所有實(shí)例共享榕栏;
  4. 創(chuàng)建子類實(shí)例時(shí)畔勤,無法向父類構(gòu)造函數(shù)傳參。

2扒磁、構(gòu)造繼承

使用父類的構(gòu)造函數(shù)來增強(qiáng)子類實(shí)例庆揪,等于是復(fù)制父類的實(shí)例屬性給子類(沒用到原型)

    function Chinese(address,name) {
        Person.call(this,name);
        this.address = address || "earth";
    }
    //Test Code
    let chinese = new Chinese('亞洲中國','北京');
    console.log(chinese.name);//北京
    console.log(chinese.address);//亞洲中國
    chinese.sleep();//北京正在偷懶~~~!
    console.log(chinese instanceof Person); //false 
    console.log(chinese instanceof Chinese); //true
    let shanghai = new Chinese('亞洲中國','上海');
    console.log(shanghai.name);//上海
    console.log(shanghai.address);//亞洲中國
    shanghai.sleep();//上海正在偷懶~~~妨托!
    console.log(shanghai instanceof Person); //false 
    console.log(shanghai instanceof Chinese); //true
    chinese.work('IT');//Uncaught TypeError: chinese.work is not a function

優(yōu)點(diǎn):

  1. 解決了1中缸榛,子類實(shí)例共享父類引用屬性的問題;
  2. 創(chuàng)建子類實(shí)例時(shí)兰伤,可以向父類傳遞參數(shù)内颗;
  3. 可以實(shí)現(xiàn)多繼承(call多個(gè)父類對(duì)象)。

缺點(diǎn):

  1. 實(shí)例并不是父類的實(shí)例敦腔,只是子類的實(shí)例均澳;
  2. 只能繼承父類的實(shí)例屬性和方法,不能繼承原型屬性/方法符衔;(上面代碼中原型方法work就會(huì)報(bào)錯(cuò))找前;
  3. 無法實(shí)現(xiàn)函數(shù)復(fù)用,每個(gè)子類都有父類實(shí)例函數(shù)的副本判族,影響性能躺盛。

3、實(shí)例繼承

為父類實(shí)例添加新特性五嫂,作為子類實(shí)例返回

    function Chinese(address,name) {
        this.address = address || "earth";
        var instance = new Person();
        instance.name = name || '北京人';
        return instance;
    }
    // Test Code
    var chinese = new Chinese('亞洲中國','上海人');
    console.log(chinese.name);//上海人
    chinese.sleep();//上海人正在偷懶~~~!
    chinese.work('IT');//上海人的工作是:IT
    console.log(chinese instanceof Person); // true
    console.log(chinese instanceof Chinese); // false

優(yōu)點(diǎn):

  1. 不限制調(diào)用方式,不管是new 子類()還是子類(),返回的對(duì)象具有相同的效果沃缘。

缺點(diǎn):

  1. 實(shí)例是父類的實(shí)例躯枢,不是子類的實(shí)例;
  2. 不支持多繼承槐臀。

4锄蹂、拷貝繼承

把父對(duì)象的屬性,全部拷貝給子對(duì)象

   function deepCopy(p, c) {
        var c = c || {};
        let mp = new p();
        for (var i in mp) {
            if (typeof mp[i] === 'object') {
                c[i] = (mp[i].constructor === Array) ? [] : {};
                deepCopy(mp[i], c[i]);
            } else {
                c[i] = mp[i];
            }
        }
        return c;
    }

    function Chinese(address) {
        this.address = address || "earth";
    }
    let chinese= new deepCopy(Person,Chinese);
     console.log(chinese.name);//上海人
    chinese.sleep();//上海人正在偷懶~~~水慨!
    chinese.work('IT');//上海人的工作是:IT
    console.log(chinese instanceof Person); // false
    console.log(chinese instanceof Chinese); // false

上面寫的是深拷貝得糜,遞歸父類所以的屬性和方法。
優(yōu)點(diǎn):

  1. 支持多繼承晰洒。

缺點(diǎn):

  1. 效率較低朝抖,內(nèi)存占用高(因?yàn)橐截惛割惖膶傩裕?/li>
  2. 無法獲取父類不可枚舉的方法(不可枚舉方法,不能使用for in 訪問到)谍珊。

5治宣、組合繼承

通過調(diào)用父類構(gòu)造,繼承父類的屬性并保留傳參的優(yōu)點(diǎn)砌滞,然后通過將父類實(shí)例作為子類原型侮邀,實(shí)現(xiàn)函數(shù)復(fù)用

 function Chinese(address,name) {
        Person.call(this,name);//第一次調(diào)用父類構(gòu)造器
        this.address = address || "earth";
    }
    Chinese.prototype = new Person();//第二次調(diào)用父類構(gòu)造器
    Chinese.prototype.constructor=Chinese;
    let chinese = new Chinese('亞洲中國','上海人');
    console.log(chinese.name);//上海人
    console.log(chinese.address);//亞洲中國
    chinese.sleep();//上海人正在偷懶~~~!
    chinese.work('IT');//上海人的工作是:IT
    console.log(chinese instanceof Person); // true
    console.log(chinese instanceof Chinese); // true

為了防止原型鏈紊亂贝润,編程時(shí)務(wù)必要遵守一點(diǎn):如果替換了prototype對(duì)象绊茧,那么,下一步必然是為新的prototype對(duì)象加上constructor屬性打掘,并將這個(gè)屬性指回原來的構(gòu)造函數(shù)华畏。
特點(diǎn):

  1. 彌補(bǔ)了方式2的缺陷,可以繼承實(shí)例屬性/方法胧卤,也可以繼承原型屬性/方法
  2. 既是子類的實(shí)例唯绍,也是父類的實(shí)例
  3. 不存在引用屬性共享問題
  4. 可傳參
  5. 函數(shù)可復(fù)用

缺點(diǎn):

  1. 調(diào)用了兩次父類構(gòu)造函數(shù),生成了兩份實(shí)例(子類實(shí)例將子類原型上的那份屏蔽了)

6枝誊、 原型繼承(很多文章喜歡叫寄生組合繼承)

通過這種方式况芒,砍掉父類的實(shí)例屬性,這樣叶撒,在調(diào)用兩次父類的構(gòu)造的時(shí)候绝骚,就不會(huì)初始化兩次實(shí)例方法/屬性,避免的組合繼承的缺點(diǎn)

  function Chinese(address, name) {
        Person.call(this, name);//第一次調(diào)用父類構(gòu)造器
        this.address = address || "earth";
    }

    (function () {
        // 創(chuàng)建一個(gè)沒有實(shí)例方法的類
        var Super = function () { };
        Super.prototype = Person.prototype;
        //將實(shí)例作為子類的原型
        Chinese.prototype = new Super();
    })();

    Chinese.prototype.constructor = Chinese;
    let chinese = new Chinese('亞洲中國', '北京人');
    console.log(chinese.name);//北京人
    console.log(chinese.address);//亞洲中國
    chinese.sleep();//北京人正在偷懶~~~祠够!
    chinese.work('IT');//北京人的工作是:IT
    console.log(chinese instanceof Person); // true
    console.log(chinese instanceof Chinese); // true

這種方式是最好的压汪,但是實(shí)現(xiàn)有點(diǎn)復(fù)雜,需要寄托一個(gè)臨時(shí)的類古瓤,這樣在改變子類的prototype對(duì)象時(shí)止剖,僅僅實(shí)例化了一個(gè)擁有父類prototype對(duì)象的類腺阳,并沒有實(shí)例化父類的方法和屬性。
歸根到底穿香,就是想辦法讓子類擁有父類的實(shí)例屬性和方法亭引,還有父類的原型屬性和方法。

總結(jié)

JavaScript的原型繼承實(shí)現(xiàn)的最佳方式就是:
1皮获、定義子類的構(gòu)造函數(shù)焙蚓,并在內(nèi)部用call()調(diào)用希望“繼承”的構(gòu)造函數(shù),并綁定this洒宝;
2购公、借助中間函數(shù)F實(shí)現(xiàn)原型鏈繼承,最好通過封裝的函數(shù)完成(可以復(fù)用)雁歌;

function inherits(Child, Parent) {
    var F = function () {};
    F.prototype = Parent.prototype;
    Child.prototype = new F();
    Child.prototype.constructor = Child;
}

3宏浩、繼續(xù)在子類的構(gòu)造函數(shù)的原型上定義新方法。

7将宪、 Object.create()(終極大招)

Object.create()方法創(chuàng)建一個(gè)新對(duì)象绘闷,使用現(xiàn)有的對(duì)象來提供新創(chuàng)建的對(duì)象的__proto__

     let person = {
        name: "Person",
        sleep: function(value) {
          console.log(`My name is ${this.name}. I am ${value}`);
        }
      };
      let child = Object.create(person);
      console.log(child.name);//Person
      child.name='child';
      console.log(child.name);//child
      child.sleep('sleeping!');//My name is child. I am sleeping!

上例中较坛,child通過Object.create()創(chuàng)建印蔗,原型為對(duì)象person。child具有person的所以屬性和方法丑勤。

7.1 語法
Object.create(proto, [propertiesObject])
參數(shù):

proto:新創(chuàng)建對(duì)象的原型對(duì)象华嘹。
propertiesObject:可選。如果沒有指定為 undefined法竞,則是要添加到新創(chuàng)建對(duì)象的可枚舉屬性(即其自身定義的屬性耙厚,而不是其原型鏈上的枚舉屬性)對(duì)象的屬性描述符以及相應(yīng)的屬性名稱。這些屬性對(duì)應(yīng)Object.defineProperties()方法直接在一個(gè)對(duì)象上定義新的屬性或修改現(xiàn)有屬性岔霸,并返回該對(duì)象薛躬。")的第二個(gè)參數(shù)。

返回值

一個(gè)新對(duì)象呆细,帶著指定的原型對(duì)象和屬性型宝。

第六點(diǎn)中的繼承進(jìn)行改造
 //將這個(gè)方法改造
    (function () {
        // 創(chuàng)建一個(gè)沒有實(shí)例方法的類
        var Super = function () { };
        Super.prototype = Person.prototype;
        //將實(shí)例作為子類的原型
        Chinese.prototype = new Super();
    })();
   //使用Object.create(),實(shí)現(xiàn)
   Chinese.prototype =Object.create(Person.prototype);

完整代碼如下:

      // 定義一個(gè)Person類
      function Person(name) {
        // 屬性
        this.name = name || "Person";
        // 實(shí)例方法
        this.sleep = function() {
          console.log(this.name + "正在偷懶~~~!");
        };
      }
      // 原型方法
      Person.prototype.work = function(job) {
        console.log(this.name + "的工作是:" + job);
      };
      function Chinese(address, name) {
        Person.call(this, name); //第一次調(diào)用父類構(gòu)造器
        this.address = address || "earth";
      }

      Chinese.prototype = Object.create(Person.prototype);
      Chinese.prototype.constructor = Chinese;
      let chinese = new Chinese("亞洲中國", "北京人");
      console.log(chinese.name); //北京人
      console.log(chinese.address); //亞洲中國
      chinese.sleep(); //北京人正在偷懶~~~絮爷!
      chinese.work("IT"); //北京人的工作是:IT
      console.log(chinese instanceof Person); // true
      console.log(chinese instanceof Chinese); // true

是不是比上面都要簡單趴酣,其實(shí)就兩步:
第一步使用Object.create,創(chuàng)建一個(gè)實(shí)例對(duì)象(__proto__指向父類的prototype)坑夯,然后把子類的prototype對(duì)象指向這個(gè)對(duì)象岖寞,這樣:子類的prototype指向Object.create創(chuàng)建的實(shí)例對(duì)象,這個(gè)實(shí)例對(duì)象的__proto__又指向父類的prototype柜蜈。
記得要改變子類的constructor的指向仗谆,將constructor屬性指回原來的構(gòu)造函數(shù)指巡。
第二步在之類中調(diào)用父類構(gòu)造器。
這樣就完美隶垮,繼承了父類的私有屬性和方法厌处,也繼承了父類的原型鏈上的方法和屬性。

如果你希望能繼承到多個(gè)對(duì)象岁疼,則可以使用混入的方式

//子類
function MyClass() {
     SuperClass.call(this);
     OtherSuperClass.call(this);
}
// 繼承一個(gè)父類
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它類
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function() {
     // do a thing
};

Object.assign會(huì)把 OtherSuperClass原型上的函數(shù)拷貝到 MyClass原型上,使 MyClass 的所有實(shí)例都可用 OtherSuperClass 的方法缆娃。

PS:

我們知道JavaScript的對(duì)象模型是基于原型實(shí)現(xiàn)的捷绒,特點(diǎn)是簡單,缺點(diǎn)是理解起來比傳統(tǒng)的類-實(shí)例模型要困難贯要,最大的缺點(diǎn)是繼承的實(shí)現(xiàn)需要編寫大量代碼暖侨,并且需要正確實(shí)現(xiàn)原型鏈。其實(shí)新的關(guān)鍵字class從ES6開始正式被引入到JavaScript中崇渗。class的目的就是讓定義類更簡單字逗。有興趣的可以去找些資料看看,和java的class一樣的用法宅广。

Class繼承
        class Earth {
            work(job) {
                console.log(this.name + '的工作是:' + job);
            }
        }
        class Person extends Earth {
            constructor(name) {
                super();// 記得用super調(diào)用父類的構(gòu)造方法!
                this.name = name;
            }
            sleep() {
                console.log(this.name + '正在偷懶~~~葫掉!');
            }
        }

        class Chinese extends Person {
            constructor(name, address) {
                super(name); // 記得用super調(diào)用父類的構(gòu)造方法!
                this.address = address;
            }
        }
        let mChinese = new Chinese('上海');
        mChinese.sleep();//上海正在偷懶~~~!
        mChinese.work('IT');//上海的工作是:IT

是不是很簡單o(∩_∩)o 哈哈跟狱!注意一下俭厚,不是所有的主流瀏覽器都支持ES6的class。如果一定要現(xiàn)在就用上驶臊,記得babel工具轉(zhuǎn)碼挪挤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市关翎,隨后出現(xiàn)的幾起案子扛门,更是在濱河造成了極大的恐慌,老刑警劉巖纵寝,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件论寨,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡店雅,警方通過查閱死者的電腦和手機(jī)政基,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闹啦,“玉大人沮明,你說我怎么就攤上這事∏戏埽” “怎么了荐健?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵酱畅,是天一觀的道長。 經(jīng)常有香客問我江场,道長纺酸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任址否,我火速辦了婚禮餐蔬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘佑附。我一直安慰自己樊诺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布音同。 她就那樣靜靜地躺著词爬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪权均。 梳的紋絲不亂的頭發(fā)上顿膨,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音叽赊,去河邊找鬼恋沃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛必指,可吹牛的內(nèi)容都是我干的芽唇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼取劫,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼匆笤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起谱邪,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤炮捧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后惦银,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咆课,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年扯俱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了书蚪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迅栅,死狀恐怖殊校,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情读存,我是刑警寧澤为流,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布呕屎,位于F島的核電站,受9級(jí)特大地震影響敬察,放射性物質(zhì)發(fā)生泄漏秀睛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一莲祸、第九天 我趴在偏房一處隱蔽的房頂上張望蹂安。 院中可真熱鬧,春花似錦锐帜、人聲如沸藤抡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弄兜,卻和暖如春药蜻,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背替饿。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國打工语泽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人视卢。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓踱卵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親据过。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惋砂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容