若是一個對象想要用到另外一個對象的方法屬性時,用繼承來實現無疑是最好的方法,這就像慕容家族的以彼之道還施彼身同樣,我能夠經過繼承來拿到你全部的對象和方法。通常的OO語言有接口繼承和實現繼承兩種繼承方式。js只支持實現繼承,並且實現主要經過原型鏈來實現的。app
具體實現繼承通常有六種方法:函數
1.原型鏈this
基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。每一個構造函數都有一個原型對象(prototype),原型對象都包含一個指向構造函數的指針(constructor),每個實例都包含一個紙箱原型對象的內部指針([[prorotype]])。若是讓一個原型對象鄧毅另外一個類型的實例,此時的原型對象將包含一個指向另外一個原型的指針,假如另外一個原型又是另外一個類型的實例。n那麼上述關係依然成立,如此層層遞進,就構成了實例與原型的鏈條,這就是原型鏈。es5
示例以下:spa
function Super(name){ this.prototype= true; } Super.prototype.getSupervalue= function(){ return this.prototype; } function Sub(){ this.subprototype = false; } //Sub的原型等於Super的實例,繼承Super及其原型的屬性和方法,繼承的方法屬性在原型上 Sub.prototype = new Super(); //添加新方法 Sub.prototype.getSubvalue = function(){ return this.subprototype; } //重寫超類型中的方法 Sub.prototype.getSupervalue = function(){ return false; } var instance = new Sub(); alert(instance.getSupervalue());//false
這就是原型鏈的方法,缺點是若是超類型中的屬性是引用類型,則因爲全部實例共享超類型的屬性,其實都是引用的一個引用類型的地址指針,一個實例更改超類型引用類型屬性的值,全部實例都會反映。另外一個缺點是沒法向超類型傳值。prototype
原型鏈繼承的屬性方法都在新實例的原型上。指針
2.借用構造函數code
借用構造函數的思想很簡單,在子類型構造函數的內部調用超類型構造函數。函數只不過是在特定環境中執行代碼的對象。所以經過apply()和call()方法也能夠在將要新建立的對象上執行構造函數,示例以下:對象
function Super(name){ this.colors = ['red','blue','green']; this.name = name; } function Sub(){ //經過運行Super的方法實現繼承,建立實例時繼承繼承的方法屬性都在實例上 Super.call(this,'jone'); } var instance1 = new Sub(); instance1.colors.push('black'); alert(instance1.colors); // 'red,blue,green,black' alert(instance1.name); //'jone' var instance2 = new Sub(); alert(instance1.colors); // 'red,blue,green'
借用構造函數繼承的本質是在構造函數中經過綁定this運行超類型的方法從而實現繼承,這種方法解決了原型鏈沒法傳值和引用類型屬性共享的問題,可是也帶來了新的問題,因爲方法都寫在構造函數中所以沒法複用。並且超類型原型中定義的方法屬性沒法獲得繼承。blog
借用構造函數繼承,在建立實例時運行超類型構造函數,繼承的屬性方法在實例上,沒法繼承超類型原型上的屬性方法。
3.組合繼承
組合繼承指的是將原型鏈和構造函數的技術組合到一塊,綜合兩者之長的一種繼承模式。主要思路是使用原型鏈實現對原型屬性和方法的繼承,而經過構造函數來實現對實例屬性的繼承。
function Super(name){ this.colors = ['red','blue','green']; this.name = name; } Super.prototype.sayName = function(){ console.log(this.name); } function Sub(name,age){//借用構造函數繼承,主要是建立實例時繼承,繼承在實例上 Super.call(this,name); this.age = age; } Sub.prototype = new Super();//原型鏈繼承,主要繼承原型的屬性方法 Sub.prototype.sayAge = function(){ alert(this.age); } var instance1 = new Sub('jone',29); instance1.colors.push('black'); alert(instance1.colors); // 'red,blue,green,black' instance1.sayName(); //'jone' instance1.sayAge(); //29 var instance2 = new Sub('greg',32); alert(instance2.colors); // 'red,blue,green' instance2.sayName(); //'greg' instance2.sayAge(); //32
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢,是最經常使用的繼承模式,並且instanceof 和isPrototypeOf()也可以用於識別基於組合繼承建立的對象。
組合繼承,原型鏈實現對原型屬性和方法的繼承,而經過構造函數來實現對實例屬性的繼承。
4.原型式繼承
這種方法滅有嚴格意義上的構造函數,主要是藉助原型能夠基於已有的對象建立新對象,同事還沒必要爲此建立自定義類型,主要運用瞭如下函數:
function object(o){ function F(){}//構造函數 F.prototype = o;//原型鏈繼承 return new F();//返回實例 }
前面幾種方法都須要知道構造函數,原型式繼承不須要知道構造函數,只須要一個對象實例,經過opject函數對傳入的對象執行了一次淺複製。es5經過Object。create()方法規範了原型式繼承,接受兩個參數:一個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象。傳入一個對象時與object()方法行爲相同,第二個參數與Object.definProperties()方法的第二個參數格式相同。
在沒有必要興師動衆建立構造函數只想讓一個對象與另外一個對象保持相似的狀況下,原型式繼承徹底能夠勝任。可是原型式繼承的缺點是包含引用類型的屬性始終都會被實例所共享,這點與原型鏈式繼承同樣的。
封裝一個函數能夠藉助一個已知對象實例能夠建立一個實現繼承的新實例並返回,方便快捷。
5.寄生式繼承
寄生式繼承和原型式繼承差很少,只不過在寄生式繼承的基礎上對新對象實例進行了增強。寄生式繼承的思路與寄生構造函數和工廠模式相似。建立一個僅用於封裝繼承過程的函數,該函數在內部以某種方式來加強對象,最後返回對象。示例以下:
function createAnother(original){ var clone = object(original);//經過調用函數建立一個新對象 clone.sayName = function(){//以某種方式增強這個對象 alert('hello'); }; return clone;//返回對象實例 }
在主要考慮對象而不是自定義類型和構造函數的狀況下,寄生式也是一種有用的模式。這種方式也是藉助實例來建立實現繼承的新實例。缺點是爲對象添加函數時函數沒法複用。
封裝一個函數藉助一個已知對象實例能夠建立一個實現繼承的新實例並用某種方法加強對象最後返回對象。
6.組合寄生模式
組合寄生模式就是對組合繼承方式的完善,組合繼承是js最經常使用的繼承模式,它最大的不足是不管什麼狀況下都會兩次調用超類型構造函數,而用寄生組合模式能夠避免這個問題。
function Super(name){ this.colors = ['red','blue','green']; this.name = name; } Super.prototype.sayName = function(){ console.log(this.name); } function Sub(name,age){ Super.call(this,name);//第二次調用Super() this.age = age; } Sub.prototype = new Super();//第一次調用Super() Sub.prototype.sayAge = function(){ alert(this.age); } var instance1 = new Sub('jone',29);
所謂寄生組合繼承,即經過借用構造函數繼承屬性,經過原型鏈的混成形式來繼承方法。基本思路是:沒必要爲了制定子類型的原型而調用超類型的構造函數,咱們須要的無非就是超類型原型的一個副本而已。本質上就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型。基本模式以下:
function inheritPrototype(Sub,Super){//針對構造函數 var prototype = object(Super.prototype);//建立對象 prototype.constructor = Sub;//加強對象 Sub.prototype = prototype; //指定原型 }
用寄生繼承模式來實現原型的繼承,這個函數接受兩個參數,子類型的構造函數和超類型的構造函數。建立超類型的副本,重寫constructor屬性,把新建立的對象(即副本)賦值給子類型的原型。完整示例以下:
function Super(name){ this.name = name; } Super.prototype.say = function(){ console.log(this.name); } function Sub(name,age){//借用構造函數實現示例繼承 Super.call(this,name); this.age = age; } inheritPrototype(Sub,Super);//寄生繼承實現原型繼承 var instance = new Sub('jone',28); instance.say();//jone
寄生組合模式避免了超類型構造函數的二次調用,借用寄生式繼承來使子類型原型繼承超類型原型的屬性方法。只調用了一次超類型對象的構造函數,避免了在Sub.prototype上建立多餘的、沒必要要的屬性。同時原型鏈不變,還能正常使用instanceof和isPrototypeOf()。廣泛認爲寄生組合模式是引用類型最理想的繼承模式。
寄生組合式繼承和組合繼承使用場景同樣,本質上是對組合繼承的完善。
總結一下:
原型鏈繼承是基礎,通常因爲存在引用類型屬性共用等問題,通常不單獨使用(是基礎,至關於內功心法,不能沒有,可是隻有這一個也不行)
借用構造函數,能夠完整繼承構造函數上定義的屬性,可是原型屬性沒法繼承 (比較輕快,至關於擒拿手,無內力,沒辦法繼承原型)
組合繼承,綜合原型鏈繼承和借用構造函數,比較經常使用(內功心法加擒拿比較重,至關於降龍十八掌,有反作用,兩次調用超類型構造函數)
原型式繼承,借用實例來建立繼承後的新實例(比較靈活,無需藉助構造函數,至關於葵花點穴手,出招快捷方便,可是與原型鏈繼承同樣,會有引用類型屬性共用問題)
寄生式繼承,本質和原始式繼承同樣,只是用某種方法對新實例進行了加強(增強版的點穴功一陽指)
寄生組合式繼承,組合式繼承的完善版,綜合運用借用構造函數和寄生組合式(內功心法加一陽指,武林神功六脈神劍)