JavaScript是一門基於對象的語言,不是面向對象的語言。因爲它沒有自己的類(class)!所以對象繼承的實現就尤爲重要!!!
一、原型鏈繼承
核心: 將父類的實例作爲子類的原型
function SuperType() {
this.property = true;
}
SuperType.prototype.getSuperValue = function() {
return this.property;
}
function SubType() {
this.subproperty = false;
}
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function() {
return this.subproperty;
}
var instance = new SubType();
console.log(instance.getSuperValue()); // true
缺點:
-
使用原型創建對象會存在多個實例對引用類型的操作會被篡改的問題,也就是來自原型對象的所有屬性被所有實例共享。
-
創建子類實例時,無法向父類構造函數傳參
二、借用構造函數繼承
核心:使用父類的構造函數來增強子類實例,等於是複製父類的實例屬性給子類(沒用到原型)
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
function SubType(name, age){
// 繼承自SuperType
SuperType.call(this, name);
this.age = age;
}
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
alert(instance1.name); // "Nicholas"
alert(instance1.age); // 29
缺點:
- 實例並不是父類的實例,只是子類的實例。只能繼承父類的實例屬性和方法,不能繼承原型屬性/方法
- 無法實現函數複用,每個子類都有父類實例函數的副本,影響性能
三、組合繼承
核心:使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承,這樣,既通過在原型上定義方法實現了函數複用,又能保證每個實例都有它自己的屬性。(原型鏈方法+構造函數方法)
function SuperType(name){
this.name = name;
this.colors = ["red", "blue"];
}
SuperType.prototype.sayName = function(){
alert(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(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
缺點:
- 調用了兩次父類構造函數,生成了兩份實例。子類實例將子類原型上的那份屏蔽了。
- (第一次調用
SuperType()
:給SubType.prototype
寫入兩個屬性name,color。第二次調用SuperType()
:給instance1
寫入兩個屬性name,color。)
四、原型式繼承
核心:利用一個空對象作爲中介,將某個對象直接賦值給空對象構造函數的原型。
function object(obj){
function F(){}
F.prototype = obj;
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");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
缺點:
- 原型鏈繼承多個實例的引用類型屬性指向相同,存在篡改的可能。
- 無法傳遞參數
五、寄生組合式繼承
核心:通過寄生方式,砍掉父類的實例屬性,結合借用構造函數傳遞參數實現繼承
function inheritPrototype(subType, superType){
var prototype = Object.create(superType.prototype); // 創建對象,創建父類原型的一個副本
prototype.constructor = subType; // 增強對象,彌補因重寫原型而失去的默認的constructor 屬性
subType.prototype = prototype; // 指定對象,將新創建的對象賦值給子類的原型
}
// 父類初始化實例屬性和原型屬性
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
// 借用構造函數傳遞增強子類實例屬性(支持傳參和避免篡改)
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
// 將父類原型指向子類
inheritPrototype(SubType, SuperType);
// 新增子類原型屬性
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType("xyc", 23);
var instance2 = new SubType("lxy", 23);
instance1.colors.push("2"); // ["red", "blue", "green", "2"]
instance1.colors.push("3"); // ["red", "blue", "green", "3"]
這是目前這好的一種實現方式,除了實現較爲複雜就沒有缺點。