在JavaScript中,每一個函數都有一個prototype屬性,這個屬性是一個指針,指向該函數的原型對象。這個原型對象爲全部該實例所共享。在默認狀況下,原型對象包含一個屬性叫作constructor,它指向prototype屬性所在的函數指針。html
圖片和例子來自《JavaScript高級程序設計(第三版)》。app
function Person () {} Person.prototype.name = 'Nicholas'; Person.prototype.age = 29; Person.prototype.job = 'Software Enginner'; Person.prototype.sayName = function () { alert(this.name); }; var person1 = new Person(), person2 = new Person();
可是改變原型時,可能會改變constructor,好比:函數
Dog.prototype = new Animal();
此時Dog.prototype.constructor指向構造函數Animal,若是有須要,能夠重寫constructor,好比this
Dog.prototype.constructor = Dog;
經過讓子類型的原型指向父類型的實例來實現基於原型鏈的繼承。其本質是原型搜索機制:當訪問一個實例的數據或方法時,首先在實例中尋找,實例中找不到後天然會沿着原型鏈尋找。這樣繼承以後,子類型的實例能夠共享父類型的數據和方法。spa
function SuperType (name) { this.name = name; this.age = 24; this.foo = ['bar1', 'bar2']; } SuperType.prototype.say = function () { console.log(this.name, this.age, this.foo); }; function SubType () {} SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; var sub1 = new SubType('zdy1'); var sub2 = new SubType('zdy2'); sub1.age = 23; sub1.foo.push('bar3'); sub1.say(); // undefined 23 ["bar1", "bar2", "bar3"] sub2.say(); // undefined 24 ["bar1", "bar2", "bar3"]
在上面代碼中,SubType經過原型繼承了SuperTpe,可是同時也暴露了兩個問題:prototype
其中有一句須要說明一下:設計
SubType.prototype.constructor = SubType;
在「SubType.prototype = new SuperType();」以後,「SubType.prototype.constructor」是指向SuperType的,須要把它更正回來。不然每個SubType的實例的constructor都指向了SuperType,這顯然是不科學的。指針
基於原型鏈的繼承方式不多單獨使用。htm
在子類型構造函數中調用父類型的構造函數,叫作「借用構造函數」,也能夠實現繼承。它的思想是在子類型中從新調用一遍父類型的構造函數來初始化數據和方法。對象
function SuperType (name) { this.name = name; this.age = 24; this.foo = ['bar1', 'bar2']; } SuperType.prototype.say = function () { alert(this.name); }; function SubType () { // 繼承了SuperType SuperType.apply(this, arguments); } var sub = new SubType('zdy'); console.log(sub) //SubType {name: "zdy", age: 24, foo: Array[2]} sub.say(); // Uncaught TypeError: Object #<SubType> has no method 'say'
借用構造函數的模式能夠在構造函數中傳入參數,但子類型不能共享父類型在原型上的數據和方法。因此,它也不多單獨使用。
組合繼承可謂整合了上面兩種方法的特色:
function SuperType (name) { this.name = name; this.age = 24; this.foo = ['bar1', 'bar2']; this.sex = 'other'; } SuperType.prototype.say = function () { console.log(this.name, this.age, this.foo); }; function SubType (name, sex) { SuperType.apply(this, arguments); this.sex = sex; } SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; var sub1 = new SubType('zdy1', 'male'); var sub2 = new SubType('zdy2', 'female'); sub1.age = 23; sub1.foo.push('bar3'); sub1.say(); // zdy1 23 ["bar1", "bar2", "bar3"] sub2.say(); // zdy2 24 ["bar1", "bar2"] console.log(sub1.sex, sub2.sex); // male female
組合式繼承也有不足之處,就是它實際調用了兩次父類型的構造函數(第10行和第14行),而且在子類型的構造函數中重寫(覆蓋)了原型中的屬性。因此改進的思路是,如何在不添加多餘的、被覆蓋的屬性的同時,得到父類型的原型?請看最後一種繼承方法。
《JavaScript高級程序設計(第3版)》對這種繼承方式給予了確定:
開發人員廣泛認爲寄生組合式繼承是引用類型最理想的繼承範式。
主要是它只調用了一次父類型的構造函數,因此避免了子類型在prototype上建立沒必要要的、多餘的屬性。
function inheritPrototype (subType, superType) { function F () {} F.prototype = superType.prototype; var proto = new F(); proto.constructor = subType; subType.prototype = proto; } 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); var instance = new SubType('Nicholas', 29); // SubType {name: "Nicholas", colors: Array[3], // age: 29, constructor: function, sayName: function} console.log(instance);
以上代碼改自《JavaScript高級程序設計(第3版)》,說說我我的對這種方法思路的理解。
在inheritPrototype函數中,建立一個臨時構造函數F()並實例化,來得到父類型原型對象的一個副本。由於它只複製了父類型的原型對象,從而並無包括父類型構造函數中的屬性與方法。至關於在繼承過程當中,繞了一個彎來躲避再一次初始化父類型的構造函數,因此不會在子類型的原型中存在多餘的父類型屬性。