構造函數、原型和實例的關係:每個構造函數都有一個原型對象,每個原型對象都有一個指向構造函數的指針,而每個實例都包含一個指向原型對象的內部指針,es6
原型鏈實現繼承app
基本思想:利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法,即讓原型對象等於另外一個類型的實例函數
基本模式:this
1 function SuperType(){ 2 this.property = true; 3 } 4 SuperType.prototype.getSuperValue = function(){ 5 return this.property; 6 }; 7 function SubType(){ 8 this.subproperty = false; 9 } 10 \\繼承了SuperType 11 SubType.prototype = new SuperType(); 12 13 SubType.prototype.getSubValue = function(){ 14 return this.subproperty; 15 }; 16 var instance = new SubType(); 17 alert(instance.getSuperValue()); \\true
spa
注意事項:prototype
別忘記默認的原型,全部的引用類型都繼承自Object,全部函數的默認原型都是Object的實例,所以默認原型裏都有一個指針,指向object.prototype3d
謹慎地定義方法,給原型添加方法的代碼必定要放在替換原型的語句以後,不能使用對象字面量添加原型方法,這樣會重寫原型鏈指針
原型鏈繼承的問題code
最主要的問題來自包含引用類型值的原型,它會被全部實例共享對象
第二個問題是,創造子類型的實例時,不能向超類型的構造函數中傳遞參數
基本思想:在子類型構造函數的內部調用超類型構造函數,經過使用apply()和call()方法能夠在未來新建立的對象上執行構造函數
1 function SuperType(){ 2 this.colors = ["red","blue","green"]; 3 } 4 5 function SubType(){ 6 \\借調了超類型的構造函數 7 SuperType.call(this); 8 } 9 10 var instance1 = new SubType(); 11 \\["red","blue","green","black"] 12 instance1.colors.push("black"); 13 console.log(instance1.colors); 14 15 var instance2 = new SubType(); 16 \\["red","blue","green"] 17 console.log(instance2.colors);
經過call或者apply方法,咱們其實是在未來新建立的SubType實例的環境下調用了SuperType構造函數。這樣一來,就會在新SubType對象上執行SuperType函數中定義的全部對象初始化代碼,所以,每個SubType的實例都會有本身的colors對象的副本
優點:
傳遞參數
1 function Supertype(name){ 2 this.name = name; 3 } 4 5 function Subtype(){ 6 Supertype.call(this,'Annika'); 7 this.age = 21; 8 } 9 10 var instance = new Subtype; 11 console.log(instance.name); \\Annika 12 console.log(instance.age); \\29
缺點:
方法都在構造函數中定義,函數沒法複用
在超類型中定義的方法,子類型不可見,結果全部類型都只能使用構造函數模式
組合繼承
基本思想:將原型鏈和借用構造函數技術組合到一塊兒。使用原型鏈實現對原型屬性和方法的繼承,用借用構造函數模式實現對實例屬性的繼承。這樣既經過在原型上定義方法實現了函數複用,又能保證每一個實例都有本身的屬性
1 function Supertype(name){ 2 this.name = name; 3 this.colors = ["red","green","blue"]; 4 } 5 6 Supertype.prototype.sayName = function(){ 7 console.log(this.name); 8 }; 9 10 function Subtype(name,age){ 11 \\繼承屬性 12 Supertype.call(this,name); 13 this.age = age; 14 } 15 16 \\繼承方法 17 Subtype.prototype = new Supertype(); 18 Subtype.prototype.constructor = Subtype; 19 Subtype.prototype.sayAge = function(){ 20 console.log(this.age); 21 }; 22 23 var instance1 = new Subtype('Annika',21); 24 instance1.colors.push("black"); 25 \\["red", "green", "blue", "black"] 26 console.log(instance1.colors); 27 instance1.sayName(); \\Annika 28 instance1.sayAge(); \\21 29 30 var instance2 = new Subtype('Anna',22); 31 \\["red", "green", "blue"] 32 console.log(instance2.colors); 33 instance2.sayName(); \\Anna 34 instance2.sayAge(); \\22
缺點:不管在什麼狀況下,都會調用兩次超類型構造函數,一次是在建立子類型原型的時候,一次是在子類型構造函數的內部
原型式繼承
基本思想:不用嚴格意義上的構造函數,藉助原型能夠根據已有的對象建立新對象,還沒必要所以建立自定義類型,所以最初有以下函數:
1 function object(o){ 2 function F(){} 3 F.prototype = o; 4 return new F(); 5 }
從本質上講,object()對傳入其中的對象執行了一次淺複製
1 var person = { 2 name:'Annika', 3 friendes:['Alice','Joyce'] 4 }; 5 6 var anotherPerson = object(person); 7 anotherPerson.name = 'Greg'; 8 anotherPerson.friendes.push('Rob'); 9 10 var yetAnotherPerson = object(person); 11 yetAnotherPerson.name = 'Linda'; 12 yetAnotherPerson.friendes.push('Sophia'); 13 14 console.log(person.friends); //['Alice','Joyce','Rob','Sophia'] 15
在這個例子中,實際上至關於建立了person的兩個副本。
ES5新增Object.create規範了原型式繼承,接收兩個參數,一個用做新對象原型的對象和(可選的)一個爲新對象定義額外屬性的對象,在傳入一個參數的狀況下,Object.create()和object()行爲相同。
1 var person = { 2 name:'Annika', 3 friendes:['Alice','Joyce'] 4 }; 5 6 var anotherPerson = object.create(person,{ 7 name:{ 8 value:"Greg" 9 } 10 }); 11 12 \\用這種方法指定的任何屬性都會覆蓋掉原型對象上的同名屬性 13 console.log(anotherPerson.name); \\Greg
用處:創造兩個類似的對象,可是包含引用類型的值的屬性始終會共享響應的值
寄生式繼承
基本思想:寄生式繼承是與原型式繼承緊密相關的一種思路,它創造一個僅用於封裝繼承過程的函數,在函數內部以某種方式加強對象,最後再返回對象。
1 function createAnother(original){ 2 \\經過調用函數建立一個新對象 3 var clone = object(original); 4 \\以某種方式來加強對象 5 clone.sayHi = fuuction(){ 6 alert("Hi"); 7 }; 8 \\返回這個對象 9 return clone 10 }
缺點:使用寄生式繼承來爲對象添加函數,會由於作不到函數複用而下降效率,這個與構造函數模式相似
寄生組合式繼承
基本思想:經過借用構造函數來繼承屬性,經過原型鏈的混成形式來繼承方法,沒必要爲了指定子類型的原型而調用超類型的構造函數,只須要超類型的一個副本。本質上,就是使用寄生式繼承來繼承超類型的原型,而後再將結果指定給子類型的原型
1 function inheritPrototype(Subtype,supertype){ 2 var prototype = object(supertype); \\建立對象 3 prototype.constructor = subtype; \\加強對象 4 subtype.prototype = prototype; \\指定對象 5 }
所以,前面的例子能夠改成以下的形式
1 function Supertype(name){ 2 this.name = name; 3 this.colors = ["red","green","blue"]; 4 } 5 6 Supertype.prototype.sayName = function(){ 7 console.log(this.name); 8 }; 9 10 function Subtype(name,age){ 11 \\繼承屬性 12 Supertype.call(this,name); 13 this.age = age; 14 } 15 16 \\繼承方法 17 inheritPrototype(Subtype,Supertype); 18 19 Subtype.prototype.sayAge = function(){ 20 console.log(this.age); 21 };
優勢:只調用了一次supertype構造函數,所以避免在subtype.prototype上建立沒必要要的,多餘的屬性,與此同時,原型鏈還能保持不變,還能正常使用instanceof 和isPrototypeOf(),所以,寄生組合式繼承被認爲是引用類型最理想的繼承範式。
ES5的繼承能夠用下圖來歸納:
es6的繼承主要要注意的是class的繼承。
基本用法:Class之間經過使用extends關鍵字,這比經過修改原型鏈實現繼承,要方便清晰不少
1 class Colorpoint extends Point { 2 constructor(x,y,color){ 3 super(x,y); //調用父類的constructor(x,y) 4 this.color = color 5 } 6 toString(){ 7 //調用父類的方法 8 return this.color + ' ' + super.toString(); 9 } 10 }
子類必須在constructor方法中調用super方法,不然新建實例時會報錯。這是由於子類沒有本身的this對象,而是繼承父類的this對象,而後對其進行加工,若是不調用super方法,子類就得不到this對象。所以,只有調用super以後,纔可使用this關鍵字。
prototype 和__proto__
一個繼承語句同時存在兩條繼承鏈:一條實現屬性繼承,一條實現方法的繼承
1 class A extends B{} 2 A.__proto__ === B; //繼承屬性 3 A.prototype.__proto__ == B.prototype;//繼承方法
ES6的繼承能夠用下圖來歸納: