總結JS的幾種繼承
最近在學ts
的過程中又複習了一遍es5
裡面的繼承方式,相信繼承也是很多面試官喜歡問的知識點,特別有筆試題的總是要我們寫一些繼承方法哈哈哈,這裡就跟大家一起來複習和鞏固一下叭叭叭
JS繼承的實現方式
既然是要實現繼承,當然需要一個父親了,不然繼承啥是不哈哈哈
父類如下:
// 定義一個動物類
function Animal (name) {
// 屬性
this.name = name || 'Animal';
// 例項方法
this.sleep = function(){
console.log(this.name + '正在睡覺!');
}
//例項引用屬性
this.features = [];
}
// 原型方法
Animal.prototype.eat = function() {
console.log(this.name + '正在吃!');
};
複製程式碼
1、建構函式實現繼承 (又叫物件冒充實現繼承)
核心:這裡使用的原理就是在
Cat
裡面,把Animal
的this
指向改為是Cat
的this
指向,從而實現繼承
重點:用.call()
和.apply()
將父類建構函式引入子類函式(在子類函式中做了父類函式的自執行(複製))
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name); //Tom
//instanceof 判斷元素是否在另一個元素的原型鏈上
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
cat.sleep() //Tom正在睡覺!
cat.eat() //會報錯cat.eat is not a function
物件冒充可以繼承建構函式裡面的屬性和方法,沒法繼承原型鏈上的屬性和方法
複製程式碼
缺點:
1.例項並不是父類的例項,只是子類的例項
2.只能繼承父類的例項屬性和方法,不能繼承父類原型上的屬性/方法
3.無法實現函式複用,每個子類都有父類例項函式的副本,影響效能
(每個子類都有父類函式的屬性和方法的副本,當cat
呼叫Animal
上的方法時,Animal
內部的this
指向的是cat
,Animal
內部的this
上的屬性和方法都被複制到了cat
上面,如果每個子類的例項都複製一遍父類的屬性和方法,就會佔用很大的記憶體,而且當父類的方法發生改變了時,已經建立好的子類例項並不能更新方法,因為已經複製了原來的父類方法當成自己的方法了)
2、原型鏈實現繼承
核心: 將父類的例項作為子類的原型 這裡把Cat的原型改為是Animal的例項,從而實現繼承
重點:讓新例項的原型等於父類的例項。
function Cat(){
}
Cat.prototype.play = function() {
console.log(this.name + '正在玩!');
};
Cat.prototype = new Animal();
Cat.prototype.name = 'cat';
// Test Code
var cat = new Cat('zhangsan'); //缺點4傳參也沒效果
var cat1 = new Cat('lisi');
cat.name = 'Tom';
cat.features.push('red');
console.log(cat instanceof Animal); //true
console.log(cat instanceof Cat); //true
cat.play() //會報錯cat.play is not a function 缺點1 所以把play方法移動到Cat.prototype = new Animal()的後面
cat.eat() //cat正在吃! 解決了建構函式實現繼承的缺點2 <(* ̄▽ ̄*)/
cat.sleep() //cat正在睡覺!
//針對父類例項值型別成員的更改,不影響
console.log(cat.name); // "Tom"
console.log(cat1.name); // "cat"
//針對父類例項引用型別成員的更改,會通過影響其他子類例項 缺點2
console.log(cat.features); // ['red']
console.log(cat1.features); // ['red']
複製程式碼
缺點:
1.如果要為子類新增屬性或者方法,只能在new Animal()
之後,並不能放在建構函式中,如上的程式碼示例,如果新增的方法放在改變子類原型的指向之前,改變指向之後新增的方法自然就沒用了,子類的prototype
已經指向了父類了
2.子類的所有例項,共用所有的父類屬性,子類不能擁有自己的屬性,如果有多個例項時,其中一個例項修改了父類引用型別的值,那麼所有的例項都會發生改變,例如我只想其中的一個例項的features
陣列改為['red']
,那麼所有的例項該方法都會發生改變
3.不能多繼承,因為是改變了原型鏈的指向,不能指向多個父類,因此只能單繼承
4.建立子類時,無法向父類建構函式傳參,因為在改變子類的原型鏈指向之後,子類的屬性和方法是無效的
3、組合繼承(組合原型鏈繼承和借用建構函式繼承)
核心:通過呼叫父類構造,繼承父類的屬性並保留傳參的優點,然後通過將父類例項作為子類原型,實現函式複用
重點:結合了以上兩種模式的優點,傳參和複用https://www.douban.com/group/topic/198146299/
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
Cat.prototype = new Animal(); //還有另一種寫法 Cat.prototype = Animal.prototype;
// 組合繼承也是需要修復建構函式指向的。
Cat.prototype.constructor = Cat;
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); // true
複製程式碼
缺點:
這種方式呼叫了兩次父類的建構函式,生成了兩份例項,相同的屬性既存在於例項中也存在於原型中
4、拷貝繼承
function Cat(name){
var animal = new Animal();
for(var p in animal){
Cat.prototype[p] = animal[p];
}
this.name = name || 'Tom';
}
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // false
console.log(cat instanceof Cat); // true
複製程式碼
缺點:
1.無法獲取父類不可列舉的方法,這種方法是用for in
來遍歷Animal
中的屬性,例如多選框的checked
屬性,這種就是不可列舉的屬性
2.效率很低,記憶體佔用高
5、寄生組合繼承(推薦)
核心:通過寄生方式,砍掉父類的例項屬性,這樣,在呼叫兩次父類的構造的時候,就不會初始化兩次例項方法/屬性,避免的組合繼承的缺點
重點:修復了組合繼承的問題https://www.douban.com/group/topic/198335398/
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(function(){
// 建立一個沒有例項方法的類
var Super = function(){};
Super.prototype = Animal.prototype;
//將例項作為子類的原型
Cat.prototype = new Super();
})();
// Test Code
var cat = new Cat();
console.log(cat.name);
console.log(cat.sleep());
console.log(cat instanceof Animal); // true
console.log(cat instanceof Cat); //true