基礎二:javascript面向對象、建立對象、原型和繼承總結(下)

前言:此次對上篇收個尾,主要總結一下javascript的繼承。javascript

1.原型鏈

js中原型鏈是實現繼承的主要方法。基本思想是:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。咱們來簡單回顧一下之前的內容:java

  1. 每一個構造函數都有一個原型對象app

  2. 每一個原型對象都包含一個指向構造函數的指針:(constructor)函數

  3. 而實例和構造函數都有一個prototype屬性指針指向原型對象。this

  4. 假如如今咱們讓原型對象(A)等於另外一個類型的實例(b),此時至關於這個原型對象(A)總體做爲一個實例指向另外一個實例的原型對象(b的原型對象B)spa

  5. 以上就實現了繼承。prototype

看下面代碼實例:指針

function SuperType(){
        this.property = true;    //property是SuperType的實例屬性
    };
    
    SuperType.prototype.getSuperValue = function(){  //getSuperValue是SuperType的原型方法
        return this.property;
    };
    
    function SubType(){
        this.subproperty = false;
    }
    
    //讓SuperType繼承SubType
    SubType.prototype = new SuperType();
    
    SubType.prototype.getSubValue = function(){
        return this.subproperty;
    };
    
    var instance = new SubType();
    alert(instance.getSuperValue());      //true
  1. 在上面的代碼中,定義了兩個類型:SuperType和SubType。每一個類型分別有一個屬性和方法。code

  2. 經過建立SuperType的實例,並賦值給了SubType.prototype,從而實現SubType繼承了這個的實例,對象

  3. 原來存在於SuperType的實例中的全部的屬性和方法,如今也存在於SubType.prototype中了。

  4. 既然如今SubType的原型對象SubType.prototype是SuperType的實例化對象,那麼SuperType的實例屬性property就位於SubType.prototype。以下圖:

  5. 如今instance.constructor如今指向的是SuperType,圖中能夠看出來。也能夠在進行繼承以後,再進行以下步驟:
    SubType.prototype.constructor = Subtype;

原型鏈

2.完整原型鏈

全部函數的默認原型都是Object的實例,因此下圖是上面例子的完整原型鏈。
完整原型鏈

3.重寫或添加方法到超類型

(1)重寫和添加方法必須在用超類型的實例(new SuperType())替換原型(SubType.prototype)以後。

function SuperType(){
        this.property = true;    
    };
    
    SuperType.prototype.getSuperValue = function(){  
        return this.property;
    };
    
    function SubType(){
        this.subproperty = false;
    }
    
    //讓SuperType繼承SubType
    SubType.prototype = new SuperType();
    //添加新方法
    SubType.prototype.getSubValue = function(){
        return this.subproperty;
    };
    //重寫超類型中的方法
    SubType.prototype.getSuperValue = function(){
        return false;
    };

    var instance = new SubType();
    alert(instance.getSuperValue());      //false
    alert((new SuperType()).getSuperValue());   //我仿照java這麼寫,竟然返回true
  1. 重寫超類型中的方法以後,經過SuperType的實例調用getSuperValue()時,調用的就是這個從新定義的方法。

  2. 經過SuperType的實例調用getSuperValue()時,調用的就是超類型中的方法,返回true

(2)經過原型鏈實現繼承時,不能使用對象字面量建立原型方法,這樣會重寫原型鏈

function SuperType(){
        this.property = true;    
    };
    
    SuperType.prototype.getSuperValue = function(){  
        return this.property;
    };
    
    function SubType(){
        this.subproperty = false;
    }
    
    //讓SuperType繼承SubType
    SubType.prototype = new SuperType();
    
    SubType.prototype = {
        getSubValue: function(){
            return this.subproperty;
        },
        someOtherMethod: function(){
            return false;
        }
    };

    var instance = new SubType();
    alert(instance.getSuperValue());      //error

(3)原型鏈的問題

  1. 在經過原型鏈進行繼承時,原型實際上會變成另外一個類型的實例,因此原先的實例屬性也就變成了如今的原型屬性了。

  2. 如今假如原型實例的屬性是引用類型的,那麼它會直接被添加成如今的對象原型的屬性,那麼經過這個建立的實例對這個引用類型的屬性進行更改時,會當即反映在全部的實例對象上。

看下面代碼:

function SuperType(){
        this.colors = ["red","blue","green"];    
    };
    
    function SubType(){
    }
    
    //讓SubType繼承SuperType
    SubType.prototype = new SuperType();
    
    var instance1 = new SubType();
    instance1.colors.push("black");
    alert(instance1.colors);       //["red","blue","green","black"]
    
    var instance2 = new SubType();
    alert(instance2.colors);        //["red","blue","green","black"]
  1. 當SubType經過原型鏈繼承了SuperType以後,SubType.prototype就變成了SuperType的一個實例

  2. 此時SubType擁有一個本身的colors屬性就像專門建立了一個SubType.prototype.colors屬性同樣

  3. 此時SubType全部的實例話對象都會共享這個colors屬性,修改instances1的colors屬性會當即在instances2中顯示出來。

原型鏈還有一個問題:在建立子類型的實例時,不能向超類型的構造函數傳遞參數,其實是沒有辦法在不影響全部對象實例的狀況下,給超類型的構造函數傳遞參數。

4.實現繼承的其它方法

(1)借用構造函數

基本思想:

  1. 在子類型構造函數的內部調用超類型的構造函數,經過使用call()方法或者apply()方法。

例子:

function SuperType(name){
        this.name = name;
        this.colors = ["red","blue","green"];
    }
    
    function SubType(name,age){
        //繼承了SuperType,同時還傳遞了參數
        SuperType.call(this,name);
        //再爲子類型定義屬性
        this.age = age;
    }
    
    var instance1 = new SubType("Jack");
    alert(instance1.name);
    instance1.colors.push("black");
    alert(instance1.colors);       //"red,blue,green,black"
    
    var instance2 = new SubType();
    alert(instance2.colors);        //"red,blue,green"
  1. 上述代碼中解決了一個問題,就是引用類型的屬性問題,每一個實例化的子類型都有本身的特有的屬性

  2. 還存在一個問題,若是方法都定義在構造函數中,那麼方法的就不能複用。

(2)組合繼承-最經常使用的繼承模式

組合繼承的思路是:

  1. 使用原型鏈實現對原型屬性和方法的繼承,經過借用構造函數來實現對實例屬性的繼承

  2. 這樣既經過在原型上定義方法實現了函數複用,又可以保證每一個實例都有它本身的屬性。

例子:

function SuperType(name){
        this.name = name;
        this.colors = ["red","blue","green"];
    }
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };
    
    function SubType(name,age){
        //繼承SuperType的屬性
        SuperType.call(this,name);
        this.age = age;
    }
    
    //繼承SuperType的方法
    SubType.prototype = new SuperType();
    //定義子類型本身的方法
    SubType.prototype.sayAge = function(){
        alert(this.age);
    };
    
    var instance1 = new SubType("Jack",26);
    instance1.colors.push("black");
    alert(instance1.colors);       //"red,blue,green,black"
    instance1.sayName();          //Jack
    instance1.sayAge();          //26
    
    var instance2 = new SubType("Rose",23);
    alert(instance2.colors);      //"red,blue,green"
    instance2.sayName();          //Rose
    instance2.sayAge();           //23

(3)原型式繼承

思路:藉助原型能夠基於已有的對象建立新對象,還沒必要所以建立本身的自定義類型
以下:

function object(o){
        function F(){};
        F.prototype = o;
        return new F();
    }
  1. object()函數內部先建立一個臨時性的函數。

  2. 而後將傳入的對象做爲這個構造函數的原型。

  3. 最後返回這個臨時類型的餓新實例。

以下:

var person = {
        name:"Jack",
        friends:["路人甲","路人乙"]
    };
    var anotherPerson = object(person);   //此處調用上方的object方法
    anotherPerson.name = "Rose";
    anotherPerson.friends.push("路人丙");
    
    var yetPerson = object(person);
    yetPerson.name = "Rick";
    yetPerson.friends.push("路人丁");
    
    alert(person.friends);      //["路人甲","路人乙","路人丙","路人丁"]

上述person.friends不只屬於person全部,並且會被anotherPerson和yetPerson共享。
還有Object.create()方法,前面已經總結過了。

(4)寄生式繼承

思路:建立一個僅用於封裝繼承過程的函數。

function createAnother(original){
        var clone = object(original);    //調用前面的object()方法
        clone.sayHi = function(){
            alert("hi");
        };
        return clone;
    }
    
    //使用
    var person = {
        name:"Jack",
        friends:["路人甲","路人乙","路人丙"]
    };
    var anotherPerson = createAnother(person);
    anotherPerson.sayHi();       //"Hi"

5.寄生組合式繼承

(1)組合繼承存在的問題

組合繼承是js最經常使用的繼承模式,不過它有本身的不足,組合繼承最大的問題在於要調用兩次超類型的構造函數一次是建立超類型的實例賦值給子類型的原型對象時一次是子類型構造函數內部
最終子類型會包含超類型對象的所有實例屬性,可是咱們不得不在調用子類型構造函數時重寫這些屬性。

看下面例子:

function SuperType(name){
        this.name = name;
        this.colors = ["red","blue","green"];
    }
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };
    
    function SubType(name,age){
        SuperType.call(this,name);           //第二次調用
        this.age = age;
    }
    
    SubType.prototype = new SuperType();        //第一次調用
    SubType.prototype.constructor = SubType;
    SubType.prototype.sayAge = function(){
        alert(this.age);
    };
  1. 第一次調用SuperType構造函數時,SubType.prototype會獲得兩個屬性:name和colors,它們都是SuperType的實例屬性,只不過位於SubType的原型中。

  2. 當調用SubType的構造函數時,在函數內部又會調用SuperType的構造函數,又在新對象上建立了實例屬性name和colors,因而這兩個屬性就屏蔽了原型中的同名屬性。

(2)解決方法

寄生組合式繼承的思想是:沒必要爲了子類型的原型而調用超類型的構造函數,咱們所須要的無非就是超類型的一個副本而已,本質上就是使用寄生式繼承來繼承超類型的原型,把返回的結果賦值給子類型的原型。

你們必定還記得上面說的原型式繼承吧吧,將一個對象淺賦值給另外一個對象,如今也能夠把一個超類型的原型賦值給另外一個子類型原型

1.回憶一下object()函數的代碼

function object(o){
        function F(){}
        F.prototype = 0;
        return new F();
    }

2.建立一個函數,它接收兩個參數:子類型構造函數和超類型構造函數。

function inheritPrototype(subType,superType){
        var prototype = object(superType.prototype);
        prototype.constructor = subType;
        subType.prototype = prototype;
    }
  1. 上面的代碼第一步建立超類型原型的一個副本

  2. 爲建立的副本添加constructor屬性,彌補因重寫原型而失去默認的constructor屬性
    此處的重寫發生在object()函數裏面,超類型的原型superType.prototype直接賦給了F.prototype,而後object()函數又返回了F的新實例。

  3. 把建立新的對象賦值給子類型的原型

3.那麼如今來使用一下

function SuperType(name){
        this.name = name;
        this.colors = ["red","blue","green"];
    }
    SuperType.prototype.sayName = function(){
        alert(this.name);
    };
    function SubType(name,age){
        SuperType.call(this,name);
        this.age = age;
    }
    inheritPrototype(subType,SuperType);
    SubType.prototype.sayAge = function(){
        alert(this.age);
    };
  1. 上述代碼高效率,由於它只調用了一次SuperType的構造函數,所以避免了在SubType.prototype上面建立沒必要要的、多餘的屬性,

  2. 此時原型鏈還能保持不變。

以上~~~~~

相關文章
相關標籤/搜索