1. 程式人生 > >原型及繼承

原型及繼承

性能 bsp 存在 設置 [] red 共享 colors call

原型

每個函數都有一個prototype屬性,指向一個原型對象,這個對象專門保存特定類型的所有實例【共有的屬性和方法】。

所有原型對象都會自動獲得constructor屬性,指向構造函數。

在調用構造函數創建新實例對象時,會自動設置新實例的內部屬性[[Prototype]]指向構造函數的原型對象。

所有對象都有[[Prototype]]屬性,字面量對象的原型為Object.prototype。

所有子對象共有的成員屬性,都要保存在構造函數的原型對象中。即一次定義,重復使用。

技術分享

每當代碼讀取對象的某個屬性時,會執行一次搜索,目標是給定名字的屬性。搜索首先從對象實例本身開始。如果在實例中找到了給定的屬性,則立即停止繼續搜索並返回該屬性的值;如果沒有找到,則繼續搜索指針指向的原型對象。如果在原型對象中查找到該屬性,則返回該屬性的值。

實例中與原型同名的屬性會屏蔽原型中的那個屬性,所以不能在實例中修改原型中的屬性,必須在原型上修改。

組合使用構造函數和原型:構造函數用於定義實例屬性,原型用於定義方法(引用類型)和共享的屬性。這樣每個實例都會獲得一份自己獨立的實例屬性副本,彼此間互不影響。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = [“Shelby”, “Court”];
}

Person.prototype = {
    constructor: Person,
    sayName: 
function () { alert(this.name); } }; var person1 = new Person(“Nicholas”, 29, “Software Engineer”); var person2 = new Person(“Greg”, 27, “Doctor”); person1.friends.push(“Van”); alert(person1.friends); //”Shelby,Court,Van” alert(person2.friends); //”Shelby,Court” alert(person1.friends === person2.friends); //
false alert(person1.sayName === person2.sayName); //true

原型相關API:

  • 獲取原型對象:

  從構造函數獲得原型對象: 構造函數.prototype

  從子對象獲得父級原型對象:

    子對象.__proto__(有兼容性問題)

  Object.getPrototypeOf({...}) === Object.prototype

  • 判斷原型對象是否在實例的原型鏈上:

    obj.isPrototypeOf(實例) //Array.prototype.isPrototypeOf([])判斷數組

  • 自有屬性和共有屬性:

    obj.hasOwnProperty() //檢測一個屬性是否存在於實例中。

  • in關鍵字:property in obj

    在obj的原型鏈上查找指定屬性名property

  • 刪除對象的屬性:delete obj.property //不能刪除共有屬性

原型鏈

本質是重寫原型對象,讓一個類型的原型對象等於另一個類型的實例

function SuperType(){
    this.property = true;
}

SuperType.prototype.getSuperValue = function(){
    return this.property;
};

function SubType(){
    this.subproperty = false;
}

//重寫原型對象,以新的原型對象替換默認原型,實現繼承。
//不能是父類的原型對象,只能是實例。否則子類修改原型屬性時會影響父類。
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};

var instance = new SubType();
console.log(instance.getSuperValue()); //true

繼承的實現

1. 單個繼承

Object.create(Proto [,propertiesObject]);  // 使用指定的原型對象和屬性創建一個新對象。

等價於:

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

  Object.setPrototypeOf(Plane.prototype,Flyer.prototype);

  //直接設置一個對象的內部[[Prototype]]屬性到另一個對象或null,實現繼承。子類型的原型對象依然存在,子類型原型中重寫父類型fly方法,不會影響父類型的原型對象。有性能問題,慎用

2.原型鏈方式

因為有可能重寫或添加子類型的方法,為了保證之後創建的所有子類型都繼承同一個超類型,一定要在創建新實例之前修改原型對象。

  子類型構造函數.prototype=超類型的實例;

註意:

  通過原型鏈實現的繼承,不能使用對象字面量創建原型方法,因為這樣會重寫原型鏈。

  不能向超類型的構造函數中傳遞參數。

  如果原型被重寫,最好同時修改原型的constructor屬性。

  如果超類型的屬性中有引用類型值,所有實例共享一個屬性,不好。

3. 組合繼承

(1)在子類型構造函數內部用call/apply調用父類型的構造函數。

function SubType(sColor, sName) {
    SupType.apply(this, arguments); 
    this.name = sName;
}

註意:arguments指SubType接收到的所有參數,SupType從0位開始按順序讀取。所以SubType構造函數的參數中前面是SupType的參數,之後才是SubType用到的參數(SubType能夠用標識符識別參數的值,而SupType只能通過位序使用)。

(2)將父類型的實例賦值給子類型的原型,繼承父類共享的屬性和方法。

  SubType.prototype=new SuperType();
  SubType.prototype.constructor=SubType;     //原型重寫時最好同時修改

註意:組合繼承會兩次調用父類構造函數,在子類實例和原型中會有重復的屬性。

4.寄生式組合繼承

通過借用構造函數來繼承屬性,通過原型鏈的混合形式來繼承方法。不必為了指定子類型的原型而調用超類型的構造函數,只需父類型的原型副本。

//使用指定的原型對象創建一個新的空對象作為中間體,類似於Object.create() 方法

//使用指定的原型對象創建一個新的空對象作為中間體,類似於Object.create() 方法
function cloneProto(o){
    function F(){}
    F.prototype = o;
    return new F();
}

//子類型的原型為上述空對象
function inheritPrototype(subType, superType){
    var prototype = cloneProto(superType.prototype);
    prototype.constructor = subType;   
    subType.prototype = prototype; 
}

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);
};

原型及繼承