js中的實(shí)現(xiàn)繼承的幾種方式

大綱:
  • 原型鏈
  • 借用構(gòu)造函數(shù)
  • 組合繼承
  • 原型式繼承
  • 寄生式繼承
  • 寄生組合式繼承

1、原型鏈:

  • 什么是原型鏈?

原型鏈的基本思想是利用原型讓一個(gè)引用類(lèi)型繼承另一個(gè)引用類(lèi)型的屬性和方法

 function Father(){     
    this.hair = "black";
  }
  Father.prototype.getHair = function(){
    return this.hair
  }

  function Son(){
    this.eyes = "blue";
  }
//Son繼承了Father
//沒(méi)有使用Son的默認(rèn)原型,而是換了一個(gè)新原型。本質(zhì)是重寫(xiě)原型
  Son.prototype = new Father();
 
  Son.prototype.getEyes =  function(){
    return this.eyes;
  }
//Son的實(shí)例對(duì)象,并不是函數(shù)
  var Gson = new Son();
  console.log(Gson.getHair());           //black
  console.log(Gson.getEyes());         //blue
  console.log(Son.prototype.getHair());       //black
  • 繼承的實(shí)現(xiàn):

1、通過(guò)創(chuàng)建Father的實(shí)例賦值給Son.prototype實(shí)現(xiàn)的繼承。
2、實(shí)現(xiàn)的本質(zhì)是重寫(xiě)原型對(duì)象,以一個(gè)新類(lèi)型的實(shí)例取代。
3、即存在Father實(shí)例中的所有屬性和方法,現(xiàn)在也存在Son.prototype中了

  • 捋一下Object,F(xiàn)ather和Son,Gson之間的關(guān)系:

1、所有的引用類(lèi)型都默認(rèn)繼承了Object,繼承的方式也是原型鏈
2、Gson是Son的實(shí)例對(duì)象,通過(guò)var Gson = new Son()。此時(shí)Gson里的__ proto__指向Son的prototype。

  console.log(Gson.__proto__ == Son.prototype);   //true

3、Son繼承了Father,通過(guò)Son.prototype = new Father()。此時(shí)Son的prototype作為實(shí)例對(duì)象,他的__ proto__屬性指向Father的prototype

console.log(Son.prototype.__proto__ == Father.prototype);  //true

4、由于Son實(shí)現(xiàn)繼承的本質(zhì)是重寫(xiě)原型對(duì)象。Son.prototye的constructor屬性已經(jīng)不再指向Son。而是指向Father

console.log(Son.prototype.constructor == Son);   //false
console.log(Son.prototype.constructor == Father);    //true

5、所有函數(shù)的默認(rèn)原型都是Object的實(shí)例,所以默認(rèn)的原型都有__ proto__屬性指向Object.prototye。這也是所有自定義類(lèi)型都會(huì)繼承toString(),valueOf()等默認(rèn)方法的根本原因

console.log(Father.prototype.__proto__ == Object.prototype);  //true
console.log(Son.prototype.__proto__ == Object.prototype);  //true

6、Son繼承了Father,F(xiàn)ather繼承了Object。而Gson是Son的實(shí)例對(duì)象。

  • 簡(jiǎn)單圖示:


    image.png
  • 注意:

不能使用對(duì)象字面量創(chuàng)建原型方法。因?yàn)闀?huì)改變constructor的指向,會(huì)導(dǎo)致原型鏈被切斷

  • 問(wèn)題:

1、包含引用類(lèi)型值的原型。
2、在創(chuàng)建子類(lèi)型的實(shí)例時(shí),不能向超類(lèi)型的構(gòu)造函數(shù)中傳遞參數(shù)

 function Father(){
    this.colors = ["black","blue","red"]
  }
  function Son(){}

  Son.prototype = new Father();

  var Gson1 = new Son();
  Gson1.colors.push("yellow");
  console.log(Gson1.colors);   //["black", "blue", "red", "yellow"]

  var Gson2 = new Son();  
  console.log(Gson2.colors);    //["black", "blue", "red", "yellow"]
//這是因?yàn)镕ather的實(shí)例對(duì)象是Son.prototye,而prototype屬性上的方法和屬性都會(huì)共享

2、借用構(gòu)造函數(shù):(偽造對(duì)象或經(jīng)典繼承)

  • 基本思想:

在子類(lèi)型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類(lèi)型構(gòu)造函數(shù)。

  • 例子:

實(shí)際上是在Son的要?jiǎng)?chuàng)建的新實(shí)例(Gson1,Gson2)環(huán)境下調(diào)用了Father。
1、使用Father.call()方法,可以解決原型鏈包含引用類(lèi)型值的原型的問(wèn)題

 function Father(){
    this.colors = ["black","blue","red"]
  }
  function Son(){
    Father.call(this)        //this指向Son的新實(shí)例對(duì)象
  }

  var Gson1 = new Son();
  Gson1.colors.push("yellow");
  console.log(Gson1.colors);   //["black", "blue", "red", "yellow"]

  var Gson2 = new Son();  
  console.log(Gson2.colors);    //["black", "blue", "red"]

2、解決原型鏈傳遞參數(shù)問(wèn)題

function Father(name){
    this.name = name
  }
  function Son(){
    //繼承了Father,并傳遞參數(shù)
    Father.call(this,"Tom");
    //實(shí)例屬性,要在繼承之后添加
    this.age = 36;      
  }

  var Gson = new Son();
  console.log(Gson.name);     //Tom
  console.log(Gson.age);      //36 

3、組合繼承:(偽經(jīng)典繼承)

是將原型鏈和借用構(gòu)造函數(shù)相結(jié)合。
1、使用原型鏈實(shí)現(xiàn)對(duì)原型屬性和方法的繼承
2、通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承

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

1、通過(guò)在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用
2、還能保證每個(gè)實(shí)例都有它自己的屬性

  • 例子:
 function Father(){
    this.colors= ["black","blue","red"];   
  }
 
  function Son(name,age){
    //繼承屬性
    Father.call(this);
    this.name = name;
    this.age = age; 
  }

  //繼承方法
  Son.prototype = new Father();
  Son.prototype.constructor = Son;
  Son.prototype.isAge = function(){
    console.log(this.age);
}
  Son.prototype.isName = function(){
    console.log(this.name);
  }


  var Gson1 = new Son("lili",11);
  Gson1.colors.push("yellow")
  console.log(Gson1.colors);         //["black", "blue", "red", "yellow"]
  console.log(Gson1.name);         //lili
  console.log(Gson1.age);            //11

  var Gson2 = new Son("bree",20);
  console.log(Gson2.colors);       //["black", "blue", "red"]
  console.log(Gson2.name);        //bree
  console.log(Gson2.age);          //20
  • 問(wèn)題:

1、無(wú)論什么情況下,都會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù)。
2、一次是在創(chuàng)建子類(lèi)型原型的適合
3、另一次是在子類(lèi)型構(gòu)造函數(shù)內(nèi)部

4、原型式繼承

  • 思想:

借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象,同時(shí)不必因此創(chuàng)建自定義類(lèi)型。

function obj(o){ //本質(zhì)上obj()對(duì)傳入的對(duì)象執(zhí)行了一次淺復(fù)制
        function F(){}
        F.prototype = o;
        return new F();
    }
var lion = {
        name:"lion",
        friends:["giraffe","elephant","rabbit"]
    }
//將lion傳入obj中,返回一個(gè)新對(duì)象。
//這個(gè)新對(duì)象的原型指向lion
//即lion上的屬性被加到新對(duì)象上的原型上了
    var fox = obj(lion);
    fox.name = "fox";
    fox.friends.push("cat");     //friends屬性被共享了

    var wolf = obj(lion);
    wolf.name = "wolf";
    wolf.friends.push("tiger");

console.log(lion.friends); //["giraffe", "elephant", "rabbit", "cat", "tiger"]
  • Object.create方法:

1、只有一個(gè)參數(shù)時(shí),用法和obj()一樣
2、有兩個(gè)參數(shù)時(shí),用法如下:

var lion = {
        name:"lion",
        friends:["giraffe","elephant","rabbit"]
    }
//類(lèi)似描述符修改屬性
    var fox = Object.create(lion,{
        name:{
            value:"fox"
        }
    });
    console.log(fox.name);
  • 優(yōu)點(diǎn):

當(dāng)沒(méi)有必要?jiǎng)?chuàng)建構(gòu)造函數(shù),只想讓一個(gè)對(duì)象與另一個(gè)對(duì)象保持類(lèi)似的情況下,原型式繼承是完全可以勝任的。

  • 缺點(diǎn):

包含引用類(lèi)型值的屬性始終會(huì)共享相應(yīng)的值。

5、寄生式繼承:

  • 本質(zhì):

1、創(chuàng)建一個(gè)僅用于封裝繼承過(guò)程的函數(shù),該函數(shù)在內(nèi)部以某種方式來(lái)增強(qiáng)對(duì)象,最后再想真的是他做了所有工作一樣返回對(duì)象
2、與寄生構(gòu)造函數(shù)和工廠模式類(lèi)似

function obj(o){ 
        function F(){}
        F.prototype = o;
        return new F();
    }
    function clo(z){       //封裝繼承過(guò)程
        var clone = obj(z);      //繼承函數(shù)
        clone.sayHi = function(){
            alert('hi') 
        };
        return clone            //返回
    }
    var lion = {
        name:"lion",
        friends:["giraffe","elephant","rabbit"]
    }
    var zoo = clo(lion);
    zoo.sayHi()        //hi
    console.log(zoo.name); //lion
    console.log(zoo.friends); //["giraffe", "elephant", "rabbit"]
  • 注意:

obj()不是必需的,任何能夠返回新對(duì)象的函數(shù)都適用于此模式

  • 缺點(diǎn):

使用寄生式繼承來(lái)為對(duì)象添加函數(shù),會(huì)由于不能做到函數(shù)復(fù)用而降低效率,與構(gòu)造函數(shù)模式類(lèi)似。

6、寄生組合式繼承

  • 概念:

通過(guò)借用構(gòu)造函數(shù)來(lái)繼承屬性,通過(guò)原型鏈的混成形式來(lái)繼承方法

  • 基本思路:

不必為了指定子類(lèi)型的原型而調(diào)用超類(lèi)型的構(gòu)造函數(shù),我們所需要的無(wú)非就是超類(lèi)型原型的一個(gè)副本而已

  • 本質(zhì):

使用寄生式繼承來(lái)繼承超類(lèi)型的原型,然后再將結(jié)果指定給子類(lèi)型的原型。

  • 例子:
function obj(o){ 
        function F(){}
        F.prototype = o;
        return new F();
    }
    function inher(son,father){
        var pro = obj(father.prototype);  //創(chuàng)建對(duì)象
        pro.constructor = son;     //增強(qiáng)對(duì)象
        son.prototype = pro;    //指定對(duì)象
    }
    function Father(name){
        this.name = name;
        this.colors = ["black","blue","yellow"]
    }
    Father.prototype.isName = function(){
        alert(this.name)
    }
    function Son(name,age){
        Father.call(this,name);    //只調(diào)用一次Father
        this.age=age;
    }
    inher(Son,Father);
    Son.prototype.isAge=function(){
        alert(this.age)
    }
  • 優(yōu)點(diǎn):

1、只調(diào)用一次Father構(gòu)造函數(shù),也避免了再Son.prototype上面創(chuàng)建不必要的屬性。
2、原型鏈能保持不變
3、是引用類(lèi)型最理想的繼承方式

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

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