1. 程式人生 > 實用技巧 >總結JS的幾種繼承

總結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裡面,把Animalthis指向改為是Catthis指向,從而實現繼承
重點:用.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指向的是catAnimal內部的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