JS繼承的6種方式(非ES6)

本篇博客主要來整合一下JS繼承的六種方式及其優缺點。首先咱們提供一個父類:javascript

// 父類
function Person(name) {
  this.name = name;
  this.showName = function () {
    console.log(this.name);
  }
}

原型鏈繼承

本節中咱們使用到的子類以下:java

// 子類
function Staff(office) {
  this.office = office;
  this.showOffice = function(){
    console.log(this.office);
  }
}
Staff.prototype = new Person('staff');
const a = new Staff('D302');
const b = new Staff('D232');
a.showName();       // satff
a.showOffice();     // D302
b.showName();       // staff
b.showOffice();     // D232

上述語句即爲原型鏈繼承,此時,ab有一個共有屬性name,要注意此時name爲基本數據類型,而原型鏈繼承構造的子對象之間共享的是引用數據類型,若是我單獨對aname屬性進行修改,那麼bname屬性不會所以而改變。舉例說明以下:函數

Staff.prototype = new Person('staff');
const a = new Staff('D302');
const b = new Staff('D232');
a.name = 'change'
a.showName();   // change
b.showName();   // staff

可是,若是Person對象的name屬性值引用類型的話,那麼構造出的子對象的屬性就有可能會受到影響,實例以下:this

Staff.prototype = new Person([1,2,3]);
const a = new Staff('D302');
const b = new Staff('D232');
a.showName();   // [1,2,3]
b.showName();   // [1,2,3]
a.name.push(4);
a.showName();   // [1,2,3,4]
b.showName();   // [1,2,3,4]
a.name = [1,2]
a.showName();   // [1,2]
b.showName();   // [1,2,3,4]

你們能夠看到如上兩種狀況,我在往name屬性裏面push的時候ab的屬性值都發生了變化,可是我在將aname屬性從新賦值爲[1,2]時,bname值並無發生變化。這是由於進行push操做以後引用數據類型的地址值並無發生變化,而bname屬性指向的地址與aname屬性指向的地址相同,因此ab會同時發生變化。可是我將其從新複製時,aname屬性指向的地址發生了變化,b指向的仍是原來的地址,因此ab並無發生同步變化。prototype

優勢: 能經過instanceOfisPrototypeOf的檢測code

缺點對象

  1. 父對象中的屬性也變成了子對象的prototype中的公用屬性繼承

    2. 不能向父類型的構造函數中傳遞參數
             3. 只能繼承一個父類

借用構造函數

function Staff(name, office) {
  Person.call(this, name);
  this.office = office;
  this.showOffice = function(){
    console.log(this.office);
  }
}

const a = new Staff('a', 'D302');
const b = new Staff('b', 'D232');
a.showName();   // a
b.showName();   // b
console.log(a instanceof Person);  // false

經過這個實例咱們能夠看出咱們能夠向父類傳遞參數了,而且能夠繼承多個父類函數,只要調用屢次call函數便可.ip

優勢內存

  1. 能夠向父類傳遞參數
  2. 能夠實現多父類繼承
  3. 父類的屬性不會被子類共享

缺點:沒法實現對構造函數的複用

組合繼承

function Staff(name, office) {
  Person.call(this, name);
  this.office = office;
  this.showOffice = function(){
    console.log(this.office);
  }
}
Staff.prototype = new Person();
const a = new Staff('a', 'D302');
const b = new Staff('b', 'D232');
a.showName();   // a
b.showName();   // b
console.log(a instanceof Person);  // true

這種方法就是對原型鏈繼承和構造函數繼承的結合。

優勢

  1. 繼承前二者的優勢
  2. 能夠實現對構造函數的複用

缺點:調用了兩次父類構造函數,更耗內存

原型式繼承

function container(obj){
  function F() {}
  F.prototype = obj;
  return new F();
}
const child = new Person('child');
const staff = container(child);
staff.showName();  // child

這種繼承方式其實能夠看作是直接複製了父類。

缺點:沒法複用

寄生式繼承

function container(obj){
  function F() {}
  F.prototype = obj;
  return new F();
}

function Staff(obj, office) {
  const sub = container(obj);
  sub.office = office;
  sub.showOffice = function(){
    console.log(this.office);
  }
  return sub;
}
const child = new Person('child');
const a = Staff(child, 'D232');
const b = Staff(child, 'C433');
a.showName();   // child
a.showOffice(); // D232
b.showName();   // child
b.showOffice(); // C433

這種繼承方式就是在原型式繼承的基礎上又加了一個函數用來新增長屬性,解決了原型式繼承須要後續增長屬性的問題。

寄生組合繼承(經常使用)

寄生組合式繼承強化的部分就是在組合繼承的基礎上減小一次多餘的調用父類的構造函數:

function Staff(name, office) {
  Person.call(this, name);
  this.office = office;
  this.showOffice = function () {
    console.log(this.office);
  }
}
Staff.prototype = Object.create(Person.prototype);
Staff.prototype.constructor = Person;
const a = new Staff('a', 'D232');
const b = new Staff('b', 'C433');
a.showName();   // a
a.showOffice(); // D232
b.showName();   // b
b.showOffice(); // C433
相關文章
相關標籤/搜索