說到繼承,其它語言裏可能有兩種: 接口繼承是繼承方法簽名,而實現繼承則繼承實際方法。ES函數沒有簽名,不能實現接口繼承,只支持實現繼承,而實現繼承主要依靠原型鏈。(這兩句話,說來輕鬆,理解來不易,且行且珍惜吧~)。函數
因此,理解原型鏈是掌握繼承的必要條件。一個原型對象等與另外一個類型的實例this
function Parent(){ this.super = "parent"; this.friends = ["A", "B", "C"]; } Parent.prototype.getParentValue = function(){ return this.super; } function Child(){ this.sub = "Child"; } Child.prototype = new Parent(); Child.prototype.getChildValue = function(){ return this.sub; } var c1= new Child(); c1.getParentValue(); //"parent" c1.getChildValue(); //"Child" c1.constructor === Parent; //true c1.constructor === Child; //false
var c2= new Child(); c2.friends.push("D"); c2.friends //["A", "B", "C", "D"] c1.friends //["A", "B", "C", "D"]
爲何demo.constructor ===Parent;呢? 由於demo.prototype指向Parent實例,而Parent.prototype.constructor指向Parent,所以demo.constructor繼承自 Parent.prototype,因此指向Parent;spa
使用原型練實現繼承:說明:不能用對象字面量建立原型方法,由於這樣會重寫原型鏈。prototype
缺點:1.引用類型值的原型屬性會被全部實例共享,所以在構造函數中定義屬性,但經過原型繼承時,一個類型的實例會變成另外一個對象的原型。所以實例中的屬性就變成了如今的原型的屬性了。2.沒有辦法在不影響全部對象的狀況下,向超類型傳參。code
爲了解決引用類型帶來的問題——>借用構造函數(僞造對象、經典繼承 ):在子類型構造函數的內部調用超類型構造函數對象
function Parent(){ this.friends = ["A", "B", "C"]; } function Child(){ Parent.call(this); this.age = 23; } var c1 = new Child(); var c2 = new Child(); c1.friends.push("D"); c1.friends //["A", "B", "C", "D"] c2.friends //["A", "B", "C"] c2.age //23
借用構造函數的缺點:沒法避免構造函數模式的缺點,方法不能複用,並且超類原型中的方法,對於子類型是不可見的,因此只能統一使用構造函數模式。blog
爲了不這些缺點——>組合繼承(僞經典繼承):使用原型鏈實現對原型屬性和方法的繼承,而經過構造函數實現對實例屬性的繼承繼承
function Parent(name){ this.name = name || "parent"; this.friends = ["A", "B", "C"]; } Parent.prototype.sayName = function (){ return this.name; } function Child(name, age){ Parent.call(this, name); this.age = age; } Child.prototype = new Parent(); var c1 = new Child("Tom", 34); var c2 = new Child("Joe", 22); c1.friends.push("D"); c2.friends //["A", "B", "C"] c1.sayName(); //"Tom"
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優勢。成爲js中最經常使用的繼承模式。
此外還有幾種繼承模式——>原型式繼承:借用原型基於已有的對象建立新的對象,同時還沒必要所以建立自定義類型。接口
function object(o){ function F(){} F.prototype = o; return new F(); } var person = { name: "Tom", friends: ["A", "B", "C"] }; var p1= object(person); p1.name = "Marry"; p1.friends.push("D"); var p2 = object(person); p2.name = "Joe"; p2.friends.push("E"); person.friends //["A", "B", "C", "D", "E"]
object()函數中建立了一個臨時構造函數,並將傳入的對象做爲該構造函數的原型。至關於進行一次淺複製。和原型模式同樣,引用類型始終會被共享。其中ES5定義了Object.create()方法規範了原型繼承。原型鏈
所以,在不必興師動衆的建立構造函數,而只想讓一個對象與另外一個對象保持相似的狀況下,原型式模式是個不錯的選擇。
——>寄生式繼承:與原型式繼承緊密相關的一種思路,與寄生構造函數和工廠模式相似,即建立一個封裝繼承過程的函數,該函數在內部以某種方式來加強對象。
function createAnother(o){ var clone = object(o); clone.sayHi = function(){ return "HI"; }; return clone; } var person = { name: "Tom", friends: ["A", "B", "C"] }; var p = createAnother(person); p.sayHi() //"HI"
在主要考慮對象而不是自定義類型和構造函數的狀況下,寄生式也是一種有用的模式,object()非必須,任何能返回新對象的函數都適用於此模式
組合模式是最經常使用的繼承模式,可是組合模式兩次調用超類型構造函數,
爲了解決這個問題——>寄生組合模式:使用構造函數繼承屬性,經過原型鏈的混成形式繼承方法;沒必要爲了指定子類型的原型而調用超類型的構造函數,咱們可使用寄生式繼承繼承超類型的原型,而後再將結果指定給子類型的原型。
function inheritPrototype(C, P){ var prototype = object(P.prototype); prototype.constructor = C; C.prototype = prototype; } function Parent(name){ this.name = name || "parent"; this.friends = ["A", "B", "C"]; } Parent.prototype.sayName = function (){ return this.name; } function Child(name, age){ Parent.call(this, name); this.age = age; } inheritPrototype(Child, Parent); Child.prototype.sayAge = function(){ return this.age; };
組合繼承模式:集寄生模式和組合模式優勢於一身,是實現基於類型繼承最有效最理想的方式。