JavaScript實現繼承的三種方式

導讀

JavaScript中主要有三種實現繼承的方式,分別是函數

  • 構造函數繼承
  • 原型繼承
  • 組合繼承

其中前兩種方式都有其缺陷。第三種方式組合繼承則將前兩種方式結合起來,取長補短,是JS繼承最經常使用的最佳實踐。本文結合代碼和註釋逐一闡述三種繼承方式。this

構造函數繼承

構造函數繼承的關鍵: 在Child構造函數中執行Parent.call(this)prototype

function Parent(name) {
  this.name = name;
  this.hobby = [];
  this.speak = function() {
    console.log("Parent speak");
  } // 缺點1:new多個Child時,Parent構造函數中的方法會在每一個Child中拷貝一份,浪費內存
}
Parent.prototype.say = function() {
  console.log("Parent say");
} // 缺點2:Parent原型對象上的方法不會被Child繼承
function Child(name, type) {
  Parent.call(this, name); // 構造函數繼承的關鍵
  this.type = type;
}

原型繼承

原型繼承的關鍵: 設置Child原型指向ParentChild.prototype = new Parent()code

function Parent(name) {
  this.name = name;
  this.hobby = []; // 缺點:Parent的引用屬性會被全部Child實例共享,互相干擾
}
Parent.prototype.say = function() {
  console.log("Parent say");
}
function Child(type) {
  this.type = type;
}
Child.prototype = new Parent(); // 原型繼承的關鍵

組合繼承(最佳實踐)

組合繼承的關鍵:對象

  • 屬性使用構造函數繼承 —— 避免了原型繼承中Parent引用屬性被全部Child實例共享的缺陷。
  • 方法使用原型繼承 —— 避免了構造函數繼承中方法重複拷貝、浪費內存的缺陷。
function Parent(name) {
  this.name = name; 
  this.hobby = []; // 屬性放在構造函數中
}
Parent.prototype.say = function() { // 方法放在原型中
  console.log("Parent say");
}
function Child(name, type) {
  Parent.call(this, name);  // Child繼承Parent屬性(構造函數繼承)
  this.type = type;  // Child擴展屬性
}
Child.prototype = Object.create(Parent.prototype);  // Child繼承Parent方法(原型繼承)
Child.prototype.speak = function() { // Child擴展方法
  console.log("Child speak");
}
Child.prototype.constructor = Child; // 修復Child的constructor指向,不然Child的constructor會指向Parent

補充:
對於組合繼承代碼中的Child.prototype = Object.create(Parent.prototype),還有兩種常見的相似寫法是Child.prototype = Parent.prototypeChild.prototype = new Parent(),但這兩種寫法都是有缺陷的,須要避免:繼承

  • Child.prototype = Parent.prototype,修改Child.prototype就等於修改Parent.prototype,會干擾全部Parent實例。
  • Child.prototype = new Parent(),Parent構造函數重複調用了兩次(另外一處調用是Child構造函數中的Parent.call(this)),浪費效率,且若是Parent構造函數有反作用,重複調用可能形成不良後果。
相關文章
相關標籤/搜索