構造函數、原型和實例的關系:每一個構造函數都有一個原型對象,每一個原型對象都有一個指向構造函數的指針,而每一個實例都包含一個指向原型對象的內部指針,
-
原型鏈實現繼承
-
基本思想:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法,即讓原型對象等於另一個類型的實例
-
基本模式:
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
-
-
注意事項:
-
別忘記默認的原型,所有的引用類型都繼承自Object,所有函數的默認原型都是Object的實例,因此默認原型里都有一個指針,指向object.prototype
-
謹慎地定義方法,給原型添加方法的代碼一定要放在替換原型的語句之后,不能使用對象字面量添加原型方法,這樣會重寫原型鏈
-
-
原型鏈繼承的問題
-
最主要的問題來自包含引用類型值的原型,它會被所有實例共享
-
第二個問題是,創造子類型的實例時,不能向超類型的構造函數中傳遞參數
-
-
-
基本思想:在子類型構造函數的內部調用超類型構造函數,通過使用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的繼承可以用下圖來概括: