js中的繼承詳解
js中的繼承
假設我們有一個Animal類,我們想構造Cat類,Cat類可以繼承Animal類的屬性和方法。以這個場景為列,我來講一講我所理解的js的繼承。
- 構造繼承
function Animal(name){ this.name = name; this.age = 15; } function Cat(name){ Animal.call(this, name); this.catName = 'cat'; } let o1 = new Cat('test1'); console.log(o1); //Cat {name: "test1", age: 15, catName: "cat"}
這就是構造繼承,在子類的建構函式中,呼叫父類的建構函式,這樣父類建構函式中的屬性就出現在了子類的建構函式中。
缺點:子類無法繼承父類原型上的方法。
Animal.prototype.say = function(){
console.log(this.name)
}
o1.say;//undefined
- 原型繼承
function Cat2(name){ this.catName = name; } Cat2.prototype = new Animal(); let o2 = new Cat2('test2'); console.log(o2); // Cat2 {catName: "test2"} o2.name;//undefined o2.age;//15 o2.say; //ƒ (){ // console.log(this.name) //}
這種方法的缺點是無法進行父類傳參初始化屬性的繼承,而且繼承的父類的屬性是所有子類共享的new Animal()這個物件例項上的屬性
,這就會導致如果更改了該物件例項的屬性,那麼這個影響就是所有子類例項共享的。
我們對父類做出如下更改
function Animal(name){ this.name = name; this.age = 15; this.friend = [1,2,3]; } function Cat21(name){ this.catName = name; } Cat21.prototype = new Animal(); let o3 = new Cat21('o3'); let o4 = new Cat21('o4'); console.log('o3.age',o3.age); console.log('o4.age',o4.age); console.log('o3.friend',o3.friend); console.log('o4.friend',o4.friend); //更改陣列friend o3.friend.push(4); console.log('o3.friend',o3.friend); console.log('o4.friend',o4.friend); //更改屬性age o3.age = 19; console.log('o3.age', o3.age); console.log('o4.age', o4.age);
輸出如下
o3.age 15
o4.age 15
o3.friend (3) [1, 2, 3]
o4.friend (3) [1, 2, 3]
o3.friend (4) [1, 2, 3, 4]
o4.friend (4) [1, 2, 3, 4]
o3.age 19
o4.age 15
我們可以發現更改friend時,這個更改在子類例項上都發生了改變,而更改age時,只在更改的例項上發生了變化。
我們來看看o3和o4
可以發現o3上的age屬性是直接在例項上的,而o3和o4的例項本身都是沒有friend屬性的。
這是為什麼呢?
這是因為在查詢物件的屬性和方法時,是沿著原型鏈進行查詢的,而你更改屬性時,如果這個屬性不是一個引用型別,是會直接為例項物件本身新增一個相應的屬性,如果是引用型別,是會改變所引用物件指向的內容的。 而原型鏈上的方法,是等同於非引用型別的屬性的。
因此,如果我們想要修改age,讓所有例項共享修改後的結果,我們可以這麼修改
o3.__proto__.age = 99;
當然,這個前提是子類例項上還沒有新增age屬性。
- 構造 + 原型繼承
function Animal(name){ this.name = name; this.age = 15; this.friend = [1,2,3]; } Animal.prototype.say = function(){ console.log(this.name); } function Cat3(name){ Animal.call(this, name); this.catName = 'test3'; } Cat3.prototype = new Animal(); let o31 = new Cat3('o31'); let o32 = new Cat3('o32'); console.log('o31', o31); console.log('o32', o32); console.log('o31.friend', o31.friend); console.log('o32.friend', o32.friend); o31.friend.push(4); console.log('o31.friend', o31.friend); console.log('o32.friend', o32.friend); o31.say();
得到的輸出如下:
可以發現父類的屬性和方法子類例項都可以繼承,但是還是有一個問題,那就是父類的建構函式多執行了一次,而這個多餘的操作是不必要的。
- 原型+構造+優化1
function Cat4(name){ Animal.call(this, name); this.catName = name; } Cat4.prototype = Object.create(Animal.prototype); let o41 = new Cat4('o41'); let o42 = new Cat4('o42'); console.log('o41', o41); console.log('o42', o42); console.log('o41.friend', o41.friend); console.log('o42.friend', o42.friend); o41.friend.push(4); console.log('o41.friend', o41.friend); console.log('o42.friend', o42.friend); o41.say();
如此,我們就少進行了一個父類建構函式的執行,但是這還是有問題的
o41 instanceof Cat4; //true
o41 instanceof Animal; //true
o41.constructor;//
//ƒ Animal(name){
// this.name = name;
// this.age = 15;
// this.friend = [1,2,3];
//}
也就是無法通過instanceof來確認例項物件是由父類構造還是子類構造。
- 原型+建構函式+優化2
由於instanceof
的本質就是在原型鏈上進行constructor
屬性的查詢 ,我們可以做如下優化
function Cat5(name){
Animal.call(this, name);
this.catName = name;
}
Cat5.prototype = Object.create(Animal.prototype);
Cat5.prototype.constructor = Cat5;
let o51 = new Cat5('o51');
o51.constructor;
//ƒ Cat5(name){
//Animal.call(this, name);
//this.catName = name;
//}
以上是個人總結的繼承相關的知識點,歡迎老鐵們在評論區進行補充。