1. 原型鏈javascript
原型鏈是js中實現繼承的主要方法,其基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。java
實現原型鏈有一種基本模式,其代碼大體以下:app
SubType 繼承了 SuperType,而繼承是經過建立 SuperType 的實例,並將該實例賦給 SubType.prototype 實現的。實現的本質是重寫原型對象,代之以一個新類型的實例。在下面例子中,咱們沒有使用 SubType 默認提供的原型,而是給它換了一個新原型;這個新原型就是 SuperType 的實例。函數
function SuperType(){ this.property = true; //實例屬性 } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } //繼承了 SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function (){ return this.subproperty; }; var instance = new SubType(); alert(instance.getSuperValue()); //true
注意:instance.constructor 如今指向的是 SuperType,這是由於原來 SubType.prototype 中的 constructor 被重寫了的緣故。實際上,不是 SubType 的原型的 constructor 屬性被重寫了,而是 SubType 的原型指向了另外一個對象—— SuperType 的原型,而這個原型對象的 constructor 屬性指向的是 SuperType。this
因爲原型鏈的關係,咱們能夠說 instance 是 Object、SuperType 或 SubType 中任何一個類型的實例。spa
原型鏈繼承的問題:1)包含引用類型值的原型。包含引用類型值的原型屬性會被全部實例共享;(以下例)2):在建立子類型的實例時,不能向超類型的構造函數中傳遞參數。實踐中不多會單獨使用原型鏈prototype
function SuperType(){ this.colors = ["red", "blue", "green"]; } function 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"
2. 借用構造函數(僞造對象或經典繼承)設計
基本思想:在子類型構造函數的內部調用超類型構造函數。對象
函數只不過是在特定環境中執行代碼的對象, 所以經過使用 apply()和 call()方法也能夠在(未來)新建立的對象上執行構造函數。blog
function SuperType(){ this.colors = ["red", "blue", "green"]; } function SubType(){ //繼承了 SuperType SuperType.call(this); } 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"
代碼中加粗的那一行代碼「借調」了超類型的構造函數。經過使用 call()方法(或 apply()方法 也能夠),咱們其實是在(將來將要)新建立的 SubType 實例的環境下調用了 SuperType 構造函數。 這樣一來,就會在新 SubType 對象上執行 SuperType()函數中定義的全部對象初始化代碼。結果, SubType 的每一個實例就都會具備本身的 colors 屬性的副本了。
優勢:能夠在子類型構造函數中向超類型構造函 數傳遞參數。
function SuperType(name){ this.name = name; } function SubType(){ //繼承了 SuperType,同時還傳遞了參數 SuperType.call(this, "Nicholas"); //實例屬性 this.age = 29; } var instance = new SubType(); alert(instance.name); //"Nicholas"; alert(instance.age); //29
以上代碼中的 SuperType 只接受一個參數 name,該參數會直接賦給一個屬性。在 SubType 構造 函數內部調用 SuperType 構造函數時,其實是爲 SubType 的實例設置了 name 屬性。
爲了確保 SuperType 構造函數不會重寫子類型的屬性,能夠在調用超類型構造函數後,再添加應該在子類型中 定義的屬性。
缺點:若是僅僅是借用構造函數,那麼也將沒法避免構造函數模式存在的問題——方法都在構造函數中定 義,所以函數複用就無從談起了。並且,在超類型的原型中定義的方法,對子類型而言也是不可見的,結 果全部類型都只能使用構造函數模式。
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); //第二次調用 SuperType() this.age = age; } //繼承方法 SubType.prototype = new SuperType(); //第一次調用 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); alert(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Nicholas"; instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); alert(instance2.colors); //"red,blue,green" instance2.sayName(); //"Greg"; instance2.sayAge(); //27
組合繼承是 JavaScript 經常使用的繼承模式。
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢,成爲 JavaScript 中經常使用的繼 承模式。並且,instanceof 和 isPrototypeOf()也可以用於識別基於組合繼承建立的對象。
缺點:不管什麼狀況下,都會調用兩次超類型構造函數:一次是在建立子類型原型的時候,另外一次是 在子類型構造函數內部。
4. 原型式繼承
道格拉斯·克羅克福德在 2006年寫了一篇文章,題爲 Prototypal Inheritance in JavaScript (JavaScript 中的原型式繼承)。
這種方法並無使用嚴格意義上的構造函數。而是藉助原型能夠基於已有的對象建立新對象,同時還沒必要所以建立自定義類型。
例:在 object()函數內部,先建立了一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回了這個臨時類型的一個新實例。
function object(o){ function F(){} F.prototype = o; return new F(); }
從本質上講,object()對傳入其中的對象執行了一次淺複製。
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnotherPerson = object(person); yetAnotherPerson.name = "Linda"; yetAnotherPerson.friends.push("Barbie"); alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
克羅克福德主張的這種原型式繼承,要求你必須有一個對象能夠做爲另外一個對象的基礎。若是有這麼 一個對象的話,能夠把它傳遞給 object()函數,而後再根據具體需求對獲得的對象加以修改便可。
ECMAScript 5經過新增 Object.create()方法規範化了原型式繼承。這個方法接收兩個參數:一 個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象。例:
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Object.create(person, { name: { value: "Greg" } }); alert(anotherPerson.name); //"Greg"
應用場景:在沒有必要興師動衆地建立構造函數,而只想讓一個對象與另外一個對象保持相似的狀況下。
缺點:包含引用類型值的屬性始終都會共享相應的值。
5. 寄生式繼承
寄生式(parasitic)繼承是與原型式繼承緊密相關的一種思路,而且一樣也是由克羅克福德推而廣之的。
寄生式繼承的思路與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,該 函數在內部以某種方式來加強對象,後再像真地是它作了全部工做同樣返回對象。
注意:示 範繼承模式時使用的 object()函數不是必需的;任何可以返回新對象的函數都適用於此模式。
function createAnother(original){ var clone = object(original); //經過調用函數建立一個新對象 clone.sayHi = function(){ //以某種方式來加強這個對象 alert("hi"); }; return clone; //返回這個對象 }
在這個例子中,createAnother()函數接收了一個參數,也就是將要做爲新對象基礎的對象。然 後,把這個對象(original)傳遞給 object()函數,將返回的結果賦值給 clone。再爲 clone 對象 添加一個新方法 sayHi(),後返回 clone 對象。
createAnother()函數使用:
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi(); //"hi"
這個例子中的代碼基於 person 返回了一個新對象——anotherPerson。新對象不只具備 person 的全部屬性和方法,並且還有本身的 sayHi()方法。
應用場景:主要考慮對象而不是自定義類型和構造函數的狀況。
缺點:使用寄生式繼承來爲對象添加函數,會因爲不能作到函數複用而下降效率;這一 點與構造函數模式相似。
6. 寄生組合式繼承
寄生組合式繼承,即經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法。
基本思路是:沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們所須要的無非就是超類型 原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。
寄生組合式繼承的基本模式以下所示。
function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //建立對象 prototype.constructor = subType; //加強對象 subType.prototype = prototype; //指定對象 }
這個示例中的 inheritPrototype()函數實現了寄生組合式繼承的簡單形式。這個函數接收兩 個參數:子類型構造函數和超類型構造函數。在函數內部,第一步是建立超類型原型的一個副本。第二 步是爲建立的副本添加 constructor 屬性,從而彌補因重寫原型而失去的默認的 constructor 屬性。 後一步,將新建立的對象(即副本)賦值給子類型的原型。
改寫前面例子:
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); };
這個例子的高效率體如今它只調用了一次 SuperType 構造函數,而且所以避免了在 SubType. prototype 上面建立沒必要要的、多餘的屬性。與此同時,原型鏈還能保持不變;所以,還可以正常使用 instanceof 和 isPrototypeOf()。開發人員廣泛認爲寄生組合式繼承是引用類型理想的繼承範式。
javascript高級程序設計-繼承-總結