在了解了js 中的原型鏈之后 (http://www.lxweimin.com/p/1e683a9771c3),我們再來看看js 中的幾種實現繼承的方式
一 借用構造函數實現繼承
為了解決包含引用類型值的原型屬性會被所有實例共享的問題,大神們發明了在子類型構造函數的內部調用超類型構造函數然后通過apply()和call()方法在(將來)新創建的對象上執行構造函數的方式來實現繼承,如下
function SuperType() {
this.colors = ["red", "blue", "green"];
}
function SubType() {
//調用SuperType 并且通過call()方法修正this指向
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
//"red,blue,green,black"
alert(instance1.colors);
//"red,blue,green"
var instance2 = new SubType();
alert(instance2.colors);
以上例子中在SubType()調用了SuperType()構造函數。通過使用call()方法(或apply()方法也可以),我們實際上是在(未來將要)新創建的SubType實例的環境下調用了SuperType構造函數。這樣一來,就會在新SubType對象上執行SuperType()函數中定義的所有對象初始化代碼。結果,SubType的每個實例就都會具有自己的colors屬性的副本了(互不影響)。
使用這種方式繼承的好處:
可以在子類型構造函數中向超類型構造函數傳遞參數。如下
function SuperType(name) {
this.name = name;
}
function SubType() {
//繼承了SuperType,同時還傳遞了參數
SuperType.call(this, "Nicholas");
//實例屬性
this.age = 29;
}
var instance = new SubType();
//"Nicholas";
alert(instance.name);
//29
alert(instance.age);
SuperType只接受一個參數name,該參數會直接賦給一個屬性。在SubType構造函數內部調用SuperType構造函數時,實際上是為SubType的實例設置了name屬性。為了確保SuperType構造函數不會重寫子類型的屬性,可以在調用超類型構造函數后,再添加應該在子類型中定義的屬性。
使用這種方式繼承的壞處:
1.方法都在構造函數中定義
2.在超類型的原型中定義的方法,對子類型而言也是不可見的 如下
function SuperType(name) {
this.name = name;
}
SuperType.prototype.a=function(){
alert("aaaa");
}
function SubType() {
//繼承了SuperType,同時還傳遞了參數
SuperType.call(this, "Nicholas");
//實例屬性
this.age = 29;
}
var instance = new SubType();
console.log(instance);
我們在控制臺可以看到子類型原型中無法獲取超類型的a方法
二 組合繼承
將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式,主要的思路是使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。這樣,既通過在原型上定義方法實現了函數復用,又能夠保證每個實例都有它自己的屬性。下面來看一個例子
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name, age) {
//繼承name屬性
SuperType.call(this, name);
this.age = age;
}
//繼承方法 (拼接原型鏈)
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
//"red,blue,green,black"
alert(instance1.colors);
//"Nicholas";
instance1.sayName();
//29
instance1.sayAge();
var instance2 = new SubType("Greg", 27);
//"red,blue,green"
alert(instance2.colors);
//"27";
instance2.sayAge();
//"Greg";
instance2.sayName();
我們看到現在實例可以訪問的超類型的原型上的方法了
SuperType構造函數定義了兩個屬性:name和colors。SuperType的原型定義了一個方法sayName()。Sub-Type構造函數在調用SuperType構造函數時傳入了name參數,緊接著又定義了它自己的屬性age。然后,將SuperType的實例賦值給SubType的原型,然后又在該新原型上定義了方法sayAge()。這樣一來,就可以讓兩個不同的SubType實例既分別擁有自己屬性——包括colors屬性,又可以使用相同的方法了這種方式是目前js實現繼承使用的最常見的方式
使用這種繼承方式的不足
SubType.prototype = new SuperType()的確會創建一個關聯到SubType.prototype 的新對象。但是它使用了SubType(..)的“構造函數調用”,如果函數SubType有一些副作用(比如寫日志、修改狀態、注冊到其他對象、給this添加數據屬性,等等)的話,就會影響到SubType()的“后代”。
改進方法
function SuperType(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function() {
alert(this.name);
};
function SubType(name, age) {
//繼承name屬性
SuperType.call(this, name);
this.age = age;
}
//使用Object.create 生成對象來代替new SuperType()生成的對象
SubType.prototype = Object.create(SuperType.prototype);
SubType.prototype.sayAge = function() {
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
console.log(instance1 );
這樣可以避免對SubType后代的影響
注
// ES6之前需要拋棄默認的SubType.prototype
SubType.ptototype = Object.create( SuperType.prototype );
// ES6開始可以直接修改現有的
SubType.prototypeObject.setPrototypeOf( SubType.prototype, SuperType.prototype );