原文連接:https://kongchenglc.coding.me...javascript
js的繼承機制不一樣於傳統的面嚮對象語言,採用原型鏈實現繼承,基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。理解原型鏈必須先理解原型,如下是對於原型的一些解釋:java
不管何時,只要建立了一個新函數,就會根據一組特定規則爲該函數建立一個
prototype
屬性。這個屬性指向函數的原型對象,全部原型對象都會自動得到一個constructor
屬性,這個屬性是一個指向prototype
屬性所在函數的指針。建立自定義的構造函數以後,其原型對象只會取得constructor
屬性,其餘方法都是從Object
繼承來的。當調用構造函數建立一個新實例以後,該實例的內部包含一個指針,指向構造函數的原型對象,即[[Prototype]]
,在瀏覽器中爲_proto_
數組
也就是說,構造函數和實例實際上都是存在一個指向原型的指針,構造函數指向原型的指針爲其prototype
屬性。實例也包含一個不可訪問的指針[[Prototype]]
(實際在瀏覽器中能夠用_proto_
訪問),而原型鏈的造成真正依賴的是_proto_
而非[[Prototype]]
。瀏覽器
下邊是一個最簡單的繼承方式的例子:用父類實例充當子類原型對象。app
function SuperType(){ this.property = true; this.arr = [1]; } SuperType.prototype.getSuperValue = function(){ return this.property; }; function SubType(){ this.subproperty = false; } SubType.prototype = new SuperType(); //在此繼承,SubType的prototype爲SuperType的一個實例 SubType.prototype.getSubValue = function(){ return this.subproperty; }; var instance = new SubType(); var instance2 = new SubType(); c(instance.getSuperValue()); //true c(instance.getSubValue()); //false c(instance.__proto__.prototype); //undefined //SubType繼承了SuperType,SuperType繼承了Object。 //instance的_proto_是SubType的原型對象,即SubType.prototype。 //而SubType.prototype又是SuperType的一個實例。 //則instance._proto_.prototype爲undefined, //由於SuperType的實例對象不包含prototype屬性。 instance.arr.push(2); c(instance.arr); //[1,2] c(instance2.arr); //[1,2] //子類們共享引用屬性
須要注意的一點:不管以什麼方式繼承,請謹慎使用將對象字面量賦值給原型的方法,這樣會重寫原型鏈。函數
原型鏈繼承方式的優勢在於簡單,而缺點也十分致命:this
子類之間會共享引用類型屬性prototype
建立子類時,沒法向父類構造函數傳參設計
又叫經典繼承,借用構造函數繼承的主要思想:在子類型構造函數的內部調用超類型構造函數,即用call()
或apply()
方法給子類中的this
執行父類的構造函數,使其擁有父類擁有的屬性實現繼承,這種繼承方法徹底沒有用到原型。下邊是借用構造函數的實現:指針
function SuperType(){ this.colors = ["red","blue","green"]; } function SubType(){ SuperType.call(this); //借用構造函數 } var instance1 = new SubType(); instance1.colors.push("black"); c(instance1.colors); //["red","blue","green","black"] var instance2 = new SubType(); c(instance2.colors); //["red","blue","green"]
借用構造函數,至關於將父類擁有的屬性在子類的構造函數也寫了一遍,使子類擁有父類擁有的屬性,這種方法在建立子類實例時,能夠向父類構造函數傳遞參數 。
function SuperType(name){ this.name = name; } function SubType(name){ SuperType.call(this,name); //借用構造函數模式傳遞參數 this.age = 29; } var instance = new SubType("something"); c(instance.name); //something c(instance.age); //29
借用構造函數模式,不一樣於原型式繼承和原型模式,它不會共享引用類型屬性,並且也能夠向超類型構造函數傳遞參數。可是相對的,因爲不會共享屬性,也沒法實現代碼複用,相同的函數在每一個實例中都有一份。爲了實現代碼複用,提示效率,大神們又想出了下邊的繼承方法。
組合繼承有時也叫僞經典繼承,是將原型鏈和借用構造函數的技術組合到一塊,從而發揮兩者之長的一種繼承模式。
即用原型鏈實現對原型屬性和方法的繼承(須要共享的),經過借用構造函數實現對實例屬性的繼承(不共享的)。這樣的方法實現了函數複用,並且每一個實例擁有本身的屬性。
function SuperType(name) { //父類的實例屬性 this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function() { //父類的原型屬性 c(this.name); }; function SubType(name, age) { //借用構造函數繼承實例屬性 SuperType.call(this, name); this.age = age; } SubType.prototype = new SuperType(); //原型鏈繼承原型屬性 SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function() { c(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); c(instance1.colors); //"red,blue,green,black" delete instance1.colors; //刪除從實例屬性繼承來的colors,讀取colors會成爲從原型繼承來的實例屬性 c(instance1.colors); //"red,blue,green" instance1.sayName(); //Nicholas instance1.sayAge(); //29 var instance2 = new SubType("Greg", 27); c(instance2.colors); //"red,blue,green" instance2.sayName(); //Greg instance2.sayAge(); //27
這是全部繼承方式中最經常使用的,它的優勢也十分明顯:
能夠在建立子類實例時向父類構造函數傳參。
引用類型屬性的值能夠不共享。
能夠實現代碼複用,便可以共享相同的方法。
可是這種方法依然有一點不足,調用了兩次父類的構造函數,最後會講到一種理論上接近完美的繼承方式,即寄生組合式繼承。
原型式繼承藉助原型基於已有對象建立新對象,須要一個對象做爲另外一個對象的基礎:
function object(o) { function F() {} F.prototype = o; return new F(); }
上邊段代碼就是原型式繼承的核心代碼,先建立一個臨時性的構造函數,而後將傳入的對象做爲這個構造函數的原型,最後返回這個臨時類型的一個新實例。
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"); c(person.friends); //["Shelby","Court","Van","Rob","Barbie"] c(person.name); //Nicholas c(anotherPerson.name); //Greg c(yetAnotherPerson.name); //Linda delete yetAnotherPerson.name; //刪除子類的屬性,就會解除對父類屬性的屏蔽,暴露出父類的name屬性 c(yetAnotherPerson.name); //Nicholas
從上邊的代碼顯示,由object
(注意首字母小寫,不是對象的構造函數)產生的兩個子類會共享父類的引用屬性,其中friends數組是共享的,anotherPerson和yetAnotherPerson都是繼承自person。實際上至關於建立了兩個person
對象的副本,但能夠在產生以後擁有各自的實例屬性。
ECMAScript5新增了Object.create()方法規範化了原型式繼承,這個方法接受兩個參數:
一個做爲新對象原型的對象(能夠是對象或者null)
另外一個爲新對象定義額外屬性的對象(可選,這個參數的格式和Object.defineProperties()方法的第二個參數格式相同,每一個屬性都是經過本身的描述符定義的)
下邊是一個例子:
var person = { //原型式繼承規範化爲create()函數 name: "Nicholas", friends: ["Shelby","Court","Van"] }; var anotherPerson = Object.create(person, { name: { value: "Greg" } }); c(anotherPerson.name); //"Greg"
若是想讓一個對象與另外一個對象保持相似,原型式繼承是很貼切的,可是與原型模式同樣,包含引用類型的值得屬性會共享相應的值。
寄生式繼承與原型式繼承緊密相關的一種思路,與寄生構造函數和工廠模式相似,即建立一個僅用於封裝繼承過程的函數,函數內部以某種方式來加強對象,最後再像真的作了全部工做同樣返回對象。
function createAnother(original) { var clone = object(original); //此處用到了原型式繼承 clone.sayHi = function() { c("Hi"); }; return clone; } var person = { //父類實例 name: "Nicholas", friends: ["Shelby","Court","Van"] }; var anotherPerson = createAnother(person); anotherPerson.sayHi();
上邊的寄生式繼承用到了原型式繼承,向實現繼承的函數傳入一個父類對象實例,再用原型式繼承獲得一個父類對象實例的副本,再給這個副本添加屬性,即加強這個對象,最後返回這個副本對象。因爲用到了原型式繼承,這個對象的原型指向傳入的父類對象實例。上邊例子用到的object()函數(原型式繼承)並非必須的,任何可以返回新對象的函數都適用於寄生式繼承模式。
寄生式繼承在主要考慮對象而不是建立自定義類型和構造函數時,是十分有用的。可是若是考慮到用寄生式繼承爲對象添加函數等,因爲沒有用到原型,作不到函數複用,會致使效率下降。
這個名字並非很貼切,雖然叫寄生組合式繼承,可是和寄生式繼承關係不是很大,主要是用原型式繼承來實現原型屬性的繼承,用借用構造函數模式繼承實例屬性。寄生組合式繼承和組合繼承的區別在於:
在繼承原型屬性時,組合繼承用原型鏈繼承了整個父類(經過將父類實例賦值給子類構造函數的原型對象來實現),這使子類中多了一份父類的實例屬性。而寄生組合式繼承用原型式繼承只繼承了父類的原型屬性(把父類構造函數的原型對象用原型式繼承複製給子類的構造函數的原型對象)。
組合繼承調用了兩次超類型構造函數,寄生組合式繼承調用了一次。
function inheritPrototype(subType, superType) { //寄生式繼承 var prototype = Object.create(superType.prototype); //建立對象 prototype.constructor = subType; //加強對象 subType.prototype = prototype; //指定對象 } function SuperType(name) { this.name = name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ c(this.name); }; function SubType(name, age) { SuperType.call(this, name); //借用構造函數 this.age = age; //添加子類獨有的屬性 } inheritPrototype(SubType, SuperType); //此處調用實現寄生組合繼承的函數 SubType.prototype.sayAge = function() { //添加子類獨有原型屬性 c(this.age); }; var son = new SubType("erzi",16); var father = new SuperType("baba"); c(typeof father.sayName); //function c(typeof father.sayAge); //SubType獨有的方法,返回undefined SubType.prototype.sayName = function() { c("This function has be changed"); } //更改子類的方法只會影響子類,prototype是對象,添加新屬性和更改屬性不會影響父類的prototype father.sayName(); //baba son.sayName(); //This function has be changed SuperType.prototype.sayName = function() { //更改父類的原型屬性 c("This function has be changed"); } father.sayName(); //This function has be changed son.sayName(); //This function has be changed
這種繼承方式理論上是完美的,可是因爲出現的較晚,人們大多數使用的是組合繼承模式。
以上就是我對於js繼承的一些理解,若有不足歡迎指出。
參考資料:《JavaScript高級程序設計》