Javascript繼承方法說明

繼承是 OOP 語言中一個比較重要的概念,繼承可使得子類具備父類的屬性和方法或者從新定義、新追加屬性和方法等,因爲 Javascript 語言沒有真正的對象類,因此其實現繼承的方法相對而言會比較特殊,實現繼承主要是依靠原型鏈來實現的。 實現繼承的方法主要有如下幾種:vue

一、原型鏈繼承

將一個原型對象的實例賦值給另外一個原型對象的原型,從而繼承該原型對象的屬性和方法。git

function SuperType() {
  this.property = true;
}

SuperType.prototype.getSuperValeu = function () {
  return this.property;
}

function SubType() {
  this.subproperty = false;
}

// 建立 SuperType 實例,並將該實例賦值給 SubType.prototype
SubType.prototype.getSubValvue = function () {
  return this.subproperty;
}

var instance = new SubType();
console.log(instance.getSuperValue());    // true
複製代碼

【注意事項】github

  1. 當子類須要覆蓋父類的方法或者添加方法時,給原型添加的方法必定要放在替換原型的語句以後, 不然子類實例調用該函數時,該函數將會被父類的原型方法給覆蓋掉。
  2. 經過原型鏈實現繼承時,不能使用對象字面量建立原型方法,不然將會重寫原型鏈,致使繼承失敗。

優勢:閉包

  • 每個子類實例均可以繼承父類函數的屬性和方法以及父類函數原型鏈上的屬性和方法

缺點:函數

  • 包含引用類型值的原型屬性會被全部實例共享,多個實例對引用類型的操做會被篡改this

    function SuperType() {
      this.colors = ['red', 'blue', 'green'];
    }
    
    function SubType() {
    }
    
    // 繼承 SuperType
    SubType.prototype = new SuperType();
    
    var instance1 = new SubType();
    instance1.colors.push('black');
    console.log(instance1.colors);    // 'red, blue, green, black'
    
    var instance2 = new SubType();
    console.log(instance2.colors);    // 'red, blue, green, black'
    複製代碼

二、借用構造函數繼承

在子類構造函數中調用執行父類構造函數,並將this指針指向子類的構造函數的做用域, 使得子類的每一個實例都會複製一份父類函數中的屬性。spa

function SuperType() {
  this.colors = ['red', 'blue' , 'green'];
}

function SubType() {
  // 執父類構造函數,繼承父類
  SuperType.call(this);
}

var instance1 = new SubType();
instance1.colors.push('blck');
console.log(instance1.colors);    // 'red, blue, green, black'

var instance2 = new SubType();
console.log(instance2.colors);    // 'red, blue, green'
複製代碼

優勢:prototype

  • 在子類的構造函數中能夠向父類函數傳遞參數設計

    function SuperType(name) {
      this.name = name;
    }
    
    function SubType() {
      // 繼承 SuperType,同時傳遞參數
      SuperType.call(this, 'Nicholas');
    
      // 實例屬性
      this.age = 29;
    }
    
    var instance = new SubType()
    console.log(instance.name);   // 'Nicholas'
    console.log(instance.age);    // 29
    複製代碼

缺點:指針

  • 只能繼承父類的實例屬性和方法,不能繼承父類原型的屬性和方法
  • 每個子類實例都有父類實例函數的副本,沒法實現屬性/方法複用

三、組合繼承

組合繼承指的是組合原型鏈和借用構造函數技術的繼承方法,使用原型鏈實現對原型屬性和方法的繼承, 經過借用構造函數來實現對實例屬性的繼承。

function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
} 

SuperType.prototype.sayName = function () {
  console.log(this.name);
};

function SubType(name, age) {
  // 繼承屬性
  // 第二次借用構造函數,調用 SuperType
  SuperType.call(this, name);

  this.age = age;
}

// 繼承方法
// 第一次構造原型鏈,調用 SuperType
SubType.prototype = new SuperType();
// 重寫 SubType.prototype 的 constructor 屬性,指向本身的構造函數 SubType
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function () {
  console.log(this.age);
};

var instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
console.log(instance1.colors);    // 'red, blue, green, black'
instance1.sayName();    // 'Nicholas'
instance1.sayAge();    // 29

var instance12 = new SubType('Greg', 27);
console.log(instance2.colors);    // 'red, blue, green'
instance2.sayName();    // 'Greg'
instance2.sayAge();    // 27
複製代碼

缺點:

  • 子類的實例對象會分別兩次調用 SuperType,在實例對象上拷貝了父類函數的屬性,同時也在原型上建立了父類函數的屬性,實例上的屬性覆蓋了原型對象上的同名屬性。

四、原型式繼承

利用空對象做爲中介,將某個對象直接複製給空對象搞糟函數的原型。

function object(obj) {
  function F() {}
  F.prototype = obj;
  return new F();
}
複製代碼

object()對傳入的對象執行了一次淺複製,將構造函數的原型直接指向傳入的對象。

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = object(person);
anotherPerson.name = 'Greg';
anotherPerson.friends.push('Rob');

var yetAnotherPerson = object(person);
yetAnotherPerson.name = 'Linda';
yetAnotherPerson.friends.push('Barbie');

console.log(person.friends);    // 'Shelby, Court, Van, Rob, Barbie'
複製代碼

另外,ES5 存在 Object.create() 的方法,可以代替上面的 object 方法。

五、寄生式繼承

在原型式繼承的基礎上,加強對象,返回構造函數

funciton createAnother(original) {
  var clone = object(orginal); // 經過調用 object() 函數建立一個新對象
  clone.sayHi = function () { // 以某種方式來加強對象
    console.log('HI);
  }
  return clone; // 返回這個對象
}
複製代碼

經過函數的做用加強新對象,即給新對象添加屬性和方法

var person = {
  name: 'Nicholas',
  friends: ['Shelby', 'Court', 'Van']
};

var anotherPerson = createAnother(person);
anotherPerson.sayHi();    // 'HI'
複製代碼

缺點:

  • 引用屬性被多個實例共享,存在多個實例篡改屬性的可能
  • 與構造函數模式相似,不能作到函數複用而下降效率

六、寄生組合式繼承

結合借用構造函數傳遞參數和寄生模實現繼承

function inheritPrototype(subType, superType) {
  var prototype = Object.create(superType.prototype); // 建立對象
  prototype.constructor = subType;                    // 加強對象
  subType.prototype = prototype;                      // 指定對象
}

function SuperType(name) {
  this.name = name;
  this.colors = ['red', 'blue', 'green'];
}

SuperType.prototype.sayName = function () {
  console.log(this.name);
};

function SubType(name, age) {
  SuperType.call(this, name);

  this.age = age;
}

inheritPrototype(SubType, SuperType);

SubType.prototype.sayAge = function () {
  console.log(this.age);
};
複製代碼

優勢:

  • 避免在實例的原型上建立沒必要要的、多餘的屬性,同時保持原型鏈不變

寄生組合式繼承式引用類型最理想的繼承範式。

七、聖盃模式繼承

聖盃模式:其原理依然遵循的是寄生組合式

// 聖盃模式
function inherit(subType, superType) {
  function F() {};
  F.prototype = superType.prototype;
  subType.prototype = new F();
  subType.prototype.constructor = superType;
  subType.prototype.uber = superType.prototype;
}

// 高級聖盃
// 經過閉包函數實現屬性私有化的做用
var inherit = (function () {
  var F = function () {};
  return function (subType, superType) {
    // 定義私有屬性
    //var prop
    F.prototype = superType.prototype;
    subType.prototype = new F();
    subType.prototype.constructor = superType;
    subType.prototype.uber = superType.prototype;
    // 獲取私有屬性
    subType.prototype.getProp = function () {
      // get prop
    }
  }
})
複製代碼

八、混入方式繼承多個對象

經過 Object.assign 把其餘原型構造函數拷貝到實例子類原型上。

function MyClass() {
  SuperClass.call(this);
  OtherSuperClass.call(this);
}

// 繼承 SuperClass 
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其餘類
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 從新指定 constructor
MyClass.prototype.constructor = MyClass;

MyClass.prototype.myMethod = function () {
  // do something
}
複製代碼

九、ES6 類繼承 extends

extends關鍵字主要用於類聲明或者類表達式中,以建立一個類,該類是另外一個類的子類。其中constructor表示構造函數,一個類中只能有一個構造函數,有多個會報出SyntaxError錯誤,若是沒有顯式指定構造方法,則會添加默認的 constructor方法

class Rectangle {
  // constructor
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
  
  // Getter
  get area() {
    return this.calcArea()
  }
  
  // Method
  calcArea() {
    return this.height * this.width;
  }
}

const rectangle = new Rectangle(10, 20);
console.log(rectangle.area);
// 輸出 200

-----------------------------------------------------------------
// 繼承
class Square extends Rectangle {

  constructor(length) {
    super(length, length);
    
    // 若是子類中存在構造函數,則須要在使用「this」以前首先調用 super()。
    this.name = 'Square';
  }

  get area() {
    return this.height * this.width;
  }
}

const square = new Square(10);
console.log(square.area);
// 輸出 100
複製代碼

extends 繼承的核心代碼以下,其實現和上述的寄生組合式繼承方式同樣

function _inherits(subType, superType) {

  // 建立對象,建立父類原型的一個副本
  // 加強對象,彌補因重寫原型而失去的默認的constructor 屬性
  // 指定對象,將新建立的對象賦值給子類的原型
  subType.prototype = Object.create(superType && superType.prototype, {
    constructor: {
      value: subType,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
  
  if (superType) {
    Object.setPrototypeOf 
      ? Object.setPrototypeOf(subType, superType) 
      : subType.__proto__ = superType;
  }
}
複製代碼

ES5繼承和ES6繼承的區別

  • ES5的繼承實質上是先建立子類的實例對象,而後再將父類的方法添加到this上(Parent.call(this)).

  • ES6的繼承有所不一樣,實質上是先建立父類的實例對象this,而後再用子類的構造函數修改this。由於子類沒有本身的this對象,因此必須先調用父類的super()方法,不然新建實例報錯。


JavaScript經常使用八種繼承方案

NicholasC.Zakas. JavaScript高級程序設計. JAVASCRIPT高級程序設計. 2012.

相關文章
相關標籤/搜索