詳解ES5和ES6的繼承

 ES5繼承

構造函數、原型和實例的關係:每個構造函數都有一個原型對象,每個原型對象都有一個指向構造函數的指針,而每個實例都包含一個指向原型對象的內部指針,es6

  • 原型鏈實現繼承app

    • 基本思想:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法,即讓原型對象等於另外一個類型的實例函數

    • 基本模式:this

     
      
     1       function SuperType(){
     2           this.property = true;
     3       }
     4       SuperType.prototype.getSuperValue = function(){
     5           return this.property;
     6       };
     7       function SubType(){
     8           this.subproperty = false;
     9       }
    10       \\繼承了SuperType
    11       SubType.prototype = new SuperType();
    12       
    13       SubType.prototype.getSubValue = function(){
    14           return this.subproperty;
    15       };
    16       var instance = new SubType();
    17       alert(instance.getSuperValue());  \\true
    • 最終結果:instance指向SubType的原型,SubType的原型又指向SuperType的原型,getSuperValue()方法任然在SuperType.prototype中,但property則位於SubType.prototype中,這是由於property是一個實例屬性,而getSuperValue是一個原型方法。此時,instance.constructor指向的是SuperType。spa

    • 注意事項:prototype

      • 別忘記默認的原型,全部的引用類型都繼承自Object,全部函數的默認原型都是Object的實例,所以默認原型裏都有一個指針,指向object.prototype3d

      • 謹慎地定義方法,給原型添加方法的代碼必定要放在替換原型的語句以後,不能使用對象字面量添加原型方法,這樣會重寫原型鏈指針

    • 原型鏈繼承的問題code

      • 最主要的問題來自包含引用類型值的原型,它會被全部實例共享對象

      • 第二個問題是,創造子類型的實例時,不能向超類型的構造函數中傳遞參數

  • 借用構造函數

    • 基本思想:在子類型構造函數的內部調用超類型構造函數,經過使用apply()和call()方法能夠在未來新建立的對象上執行構造函數

       
          
       1 function SuperType(){
       2     this.colors = ["red","blue","green"];
       3 }
       4  5 function SubType(){
       6     \\借調了超類型的構造函數
       7     SuperType.call(this);
       8 }
       9 10 var instance1 = new SubType();
      11 \\["red","blue","green","black"]
      12 instance1.colors.push("black");
      13 console.log(instance1.colors);
      14 15 var instance2 = new SubType();
      16 \\["red","blue","green"]
      17 console.log(instance2.colors);

       

      經過call或者apply方法,咱們其實是在未來新建立的SubType實例的環境下調用了SuperType構造函數。這樣一來,就會在新SubType對象上執行SuperType函數中定義的全部對象初始化代碼,所以,每個SubType的實例都會有本身的colors對象的副本

    • 優點:

      • 傳遞參數

       
          
       1 function Supertype(name){
       2     this.name = name;
       3 }
       4  5 function Subtype(){
       6     Supertype.call(this,'Annika');
       7     this.age  = 21;
       8 }
       9 10 var instance = new Subtype;
      11 console.log(instance.name);  \\Annika
      12 console.log(instance.age);   \\29

       

    • 缺點:

      • 方法都在構造函數中定義,函數沒法複用

      • 在超類型中定義的方法,子類型不可見,結果全部類型都只能使用構造函數模式

  • 組合繼承

    • 基本思想:將原型鏈和借用構造函數技術組合到一塊兒。使用原型鏈實現對原型屬性和方法的繼承,用借用構造函數模式實現對實例屬性的繼承。這樣既經過在原型上定義方法實現了函數複用,又能保證每一個實例都有本身的屬性

       
          
       1 function Supertype(name){
       2     this.name = name;
       3     this.colors = ["red","green","blue"];
       4 }
       5  6 Supertype.prototype.sayName = function(){
       7     console.log(this.name);
       8 };
       9 10 function Subtype(name,age){
      11     \\繼承屬性
      12     Supertype.call(this,name);
      13     this.age  = age;
      14 }
      15 16 \\繼承方法
      17 Subtype.prototype = new Supertype();
      18 Subtype.prototype.constructor = Subtype;
      19 Subtype.prototype.sayAge = function(){
      20     console.log(this.age);
      21 };
      22 23 var instance1 = new Subtype('Annika',21);
      24 instance1.colors.push("black");
      25 \\["red", "green", "blue", "black"]
      26 console.log(instance1.colors); 
      27 instance1.sayName(); \\Annika
      28 instance1.sayAge();  \\21
      29 30 var instance2 = new Subtype('Anna',22);
      31 \\["red", "green", "blue"]
      32 console.log(instance2.colors);
      33 instance2.sayName();   \\Anna
      34 instance2.sayAge();    \\22

       

    • 缺點:不管在什麼狀況下,都會調用兩次超類型構造函數,一次是在建立子類型原型的時候,一次是在子類型構造函數的內部

  • 原型式繼承

    • 基本思想:不用嚴格意義上的構造函數,藉助原型能夠根據已有的對象建立新對象,還沒必要所以建立自定義類型,所以最初有以下函數:

       
          
      1 function object(o){
      2     function F(){}
      3     F.prototype = o;
      4     return new F();
      5 }

       

      從本質上講,object()對傳入其中的對象執行了一次淺複製

       
          
       1 var person = {
       2     name:'Annika',
       3     friendes:['Alice','Joyce']
       4 };
       5  6 var anotherPerson = object(person);
       7 anotherPerson.name = 'Greg';
       8 anotherPerson.friendes.push('Rob');
       9 10 var yetAnotherPerson = object(person);
      11 yetAnotherPerson.name = 'Linda';
      12 yetAnotherPerson.friendes.push('Sophia');
      13 14 console.log(person.friends);   //['Alice','Joyce','Rob','Sophia']
      15

       

      在這個例子中,實際上至關於建立了person的兩個副本。

    • ES5新增Object.create規範了原型式繼承,接收兩個參數,一個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象,在傳入一個參數的狀況下,Object.create()和object()行爲相同。

       
          
       1 var person = {
       2     name:'Annika',
       3     friendes:['Alice','Joyce']
       4 };
       5  6 var anotherPerson = object.create(person,{
       7     name:{
       8         value:"Greg"
       9     }
      10 });
      11 12 \\用這種方法指定的任何屬性都會覆蓋掉原型對象上的同名屬性
      13 console.log(anotherPerson.name);   \\Greg

       

    • 用處:創造兩個類似的對象,可是包含引用類型的值的屬性始終會共享響應的值

  • 寄生式繼承

    • 基本思想:寄生式繼承是與原型式繼承緊密相關的一種思路,它創造一個僅用於封裝繼承過程的函數,在函數內部以某種方式加強對象,最後再返回對象。

       
          
       1 function createAnother(original){
       2     \\經過調用函數建立一個新對象
       3     var clone = object(original);
       4     \\以某種方式來加強對象
       5     clone.sayHi = fuuction(){
       6         alert("Hi");
       7     };
       8     \\返回這個對象
       9     return clone
      10 }

       

    • 缺點:使用寄生式繼承來爲對象添加函數,會由於作不到函數複用而下降效率,這個與構造函數模式相似

  • 寄生組合式繼承

    • 基本思想:經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法,沒必要爲了指定子類型的原型而調用超類型的構造函數,只須要超類型的一個副本。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型

       
          
      1 function inheritPrototype(Subtype,supertype){
      2     var prototype = object(supertype);   \\建立對象
      3     prototype.constructor = subtype;     \\加強對象
      4     subtype.prototype = prototype;       \\指定對象
      5 }

       

      所以,前面的例子能夠改成以下的形式

       
          
       1 function Supertype(name){
       2     this.name = name;
       3     this.colors = ["red","green","blue"];
       4 }
       5  6 Supertype.prototype.sayName = function(){
       7     console.log(this.name);
       8 };
       9 10 function Subtype(name,age){
      11     \\繼承屬性
      12     Supertype.call(this,name);
      13     this.age  = age;
      14 }
      15 16 \\繼承方法
      17 inheritPrototype(Subtype,Supertype);
      18 19 Subtype.prototype.sayAge = function(){
      20     console.log(this.age);
      21 };

       

    • 優勢:只調用了一次supertype構造函數,所以避免在subtype.prototype上建立沒必要要的,多餘的屬性,與此同時,原型鏈還能保持不變,還能正常使用instanceof 和isPrototypeOf(),所以,寄生組合式繼承被認爲是引用類型最理想的繼承範式。

總結:

ES5的繼承能夠用下圖來歸納:

 

ES6繼承

es6的繼承主要要注意的是class的繼承。

  • 基本用法:Class之間經過使用extends關鍵字,這比經過修改原型鏈實現繼承,要方便清晰不少

 
 1 class Colorpoint extends Point {
 2     constructor(x,y,color){
 3         super(x,y); //調用父類的constructor(x,y)
 4         this.color = color
 5     }
 6     toString(){
 7         //調用父類的方法
 8         return this.color + ' ' + super.toString(); 
 9     }
10 }

 

子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,而後對其進行加工,若是不調用super方法,子類就得不到this對象。所以,只有調用super以後,纔可使用this關鍵字。

  • prototype 和__proto__

一個繼承語句同時存在兩條繼承鏈:一條實現屬性繼承,一條實現方法的繼承

 
1 class A extends B{}
2 A.__proto__ === B;  //繼承屬性
3 A.prototype.__proto__ == B.prototype;//繼承方法

總結:

ES6的繼承能夠用下圖來歸納:

相關文章
相關標籤/搜索