JavaScript實現繼承的幾種方法
原型鏈繼承
prototype原型相當於一個對象的父類,所有實例都會共享原型上的屬性和方法。
constructor構造函數相當於一個創建對象的函數,同時也是對象實例的標識符。bigApple,smallApple使用Apple作爲構造函數,初始化就會使用Apple方法;同時這兩個實例對象也屬於Apple類。
// 原型鏈繼承
// fruit水果類
function Fruit() {
this.name = "水果";
this.color = ["green"];
}
Fruit.prototype.sayName = function () {
console.log(this.name);
}
function Apple() { };
// 繼承
Apple.prototype = new Fruit(); // 設置對象原型爲Fruit
Apple.prototype.constructor = Apple; // 將構造函數改爲Apple
var bigApple = new Apple();
var smallApple = new Apple();
bigApple.sayName(); // 水果
console.log(smallApple.name); // 水果
原理:重寫子類的原型對象,將父類的方法和屬性繼承到子類上
具體原型鏈如下:
對比沒有繼承前和繼承後的結果
缺點:
1.繼承的Fruit實例會被所有Apple實例(bigApple,smallApple)所共享,如果原型上有引用類型(數組,對象),一旦修改,所有Apple實例的原型都會修改
bigApple.color.push("red");
console.log(bigApple.color); // ["green", "red"]
console.log(smallApple.color); // ["green", "red"]
2.創建子類實例(smallApple,bigApple)時,不能向父類傳參。也就是說,子類初始化不能定製父類的屬性和方法,父類對象在繼承時,已經創建。每個子類實例的原型都一樣。
借用構造函數繼承
在子類構造函數內容調用父內構造函數方法
// 借用構造函數繼承
function Fruit(name) {
this.name = name;
this.color = ["green"];
}
Fruit.prototype.sayName = function () {
console.log(this.name);
}
function Apple(name) {
Fruit.call(this,name);
};
const bigApple = new Apple('big');
const smallApple = new Apple('small');
bigApple.color.push("red");
console.log(bigApple.color); // ["green", "red"]
console.log(smallApple.color); // ["green"]
console.log(bigApple.sayName); //undefined
修改Fruit構造函數的this指向,將其指向Apple的實例對象,給Apple實例初始化
原理:改變父類函數的this指向
優點:實例的屬性不會共享,子類構造函數可以傳遞參數給父類
缺點:父類定義的方法不能被子類繼承下來
組合繼承(終點)
爲了解決上述,父類的方法不能被繼承下來的缺點
// 借用構造函數繼承
function Fruit(name) {
this.name = name;
this.color = ["green"];
}
Fruit.prototype.sayName = function () {
console.log(this.name);
}
function Apple(name) {
Fruit.call(this,name);
};
Apple.prototype = new Fruit();
Apple.prototype.constructor = Apple;
const bigApple = new Apple('big');
const smallApple = new Apple('small');
bigApple.color.push("red");
console.log(bigApple.color); // ["green", "red"]
console.log(smallApple.color); // ["green"]
bigApple.sayName(); //big
只要子類原型對象設置爲父類實例時,子類就可以繼承父類原型上的方法。
注意:修改子類原型時,必須將原型上構造函數的指向子類對象。否則實例化子類對象時,會用父類的Fruit方法去構造對象;而不是Apple方法。
優點:解決了子類繼承父類方法的問題
缺點:調用兩次父類構造函數方法(Fruit.call(this,name);
和new Fruit()
)
寄生組合式繼承(重點)
針對上面連詞使用父類構造函數方法改進
可以使用Object.create()
方法替代
function Fruit(name) {
this.name = name;
this.color = ["green"];
}
Fruit.prototype.sayName = function () {
console.log(this.name);
}
function Apple(name) {
Fruit.call(this,name);
};
Apple.prototype = Object.create(Fruit.prototype);
Apple.prototype.constructor = Apple;
const bigApple = new Apple('big');
const smallApple = new Apple('small');
Object.create()
方法,創建一個新對象,將傳入的對象Fruit.prototype
作爲父類
原理:將子類的原型對象 指向 父類的原型對象
多重繼承
理論上js不允許一個對象繼承多個對象
Object.assign(); 可以將參數二對象上的屬性和方法 複製給 參數一對象
function Fruit(name) {
this.name = name;
this.color = ["green"];
}
Fruit.prototype.sayName = function () {
console.log(this.name);
}
function Plant(){
this.desc = "植物類"
}
Plant.prototype.sayHi = function () {
console.log("hi");
}
function Apple(name) {
Fruit.call(this,name);
Plant.call(this);
};
Apple.prototype = Object.create(Fruit.prototype);
// 將植物類原型上的屬性和方法複製給Apple原型
Object.assign(Apple.prototype,Plant.prototype);
Apple.prototype.constructor = Apple;
const bigApple = new Apple('big');
bigApple.sayName(); // big
bigApple.sayHi(); // hi