JavaScript學習筆記(十四) 繼承

這篇文章將會介紹在 JavaScript 中經常使用的六種繼承方式

1.1 原型繼承

方法:將子類的原型指向父類的實例

原理:子類在訪問屬性或調用方法時,往上查找原型鏈,能夠找到父類的屬性和方法

function SuperType(name, info) {
    // 實例屬性(基本類型)
    this.name = name || 'Super'
    // 實例屬性(引用類型)
    this.info = info || ['Super']
    // 實例方法
    this.getName = function() { return this.name }
}
// 原型方法
SuperType.prototype.getInfo = function() { return this.info }

// 原型繼承
function ChildType(message) { this.message = message }
ChildType.prototype = new SuperType('Child', ['Child'])

// 在調用子類構造函數時,無法向父類構造函數傳遞參數
var child = new ChildType('Hello')

// 子類實例可以訪問父類的實例方法和原型方法
console.log(child.getName()) // Child
console.log(child.getInfo()) // ["Child"]

// 所有子類實例共享父類的引用屬性
var other = new ChildType('Hi')
other.info.push('Temp')
console.log(other.info) // ["Child", "Temp"]
console.log(child.info) // ["Child", "Temp"]
  • 缺點:在調用子類構造函數時,無法向父類構造函數傳遞參數
  • 優點:子類實例可以訪問父類的實例方法和原型方法
  • 缺點:所有子類實例共享父類的引用屬性

1.2 構造繼承

方法:在子類的構造函數調用父類的構造函數,並將 this 指向子類實例

原理:在構造子類時,調用父類的構造函數初始化子類的屬性和方法

function SuperType(name, info) {
    // 實例屬性(基本類型)
    this.name = name || 'Super'
    // 實例屬性(引用類型)
    this.info = info || ['Super']
    // 實例方法
    this.getName = function() { return this.name }
}
// 原型方法
SuperType.prototype.getInfo = function() { return this.info }

// 構造繼承
function ChildType(name, info, message) {
    SuperType.call(this, name, info)
    this.message = message
}

// 在調用子類構造函數時,可以向父類構造函數傳遞參數
var child = new ChildType('Child', ['Child'], 'Hello')

// 子類實例可以訪問父類的實例方法,但是不能訪問父類的原型方法
console.log(child.getName()) // Child
console.log(child.getInfo()) // Uncaught TypeError

// 每個子類實例的屬性獨立存在
var other = new ChildType('Child', ['Child'], 'Hi')
other.info.push('Temp')
console.log(other.info) // ["Child", "Temp"]
console.log(child.info) // ["Child"]
  • 優點:在調用子類構造函數時,可以向父類構造函數傳遞參數
  • 缺點:子類實例可以訪問父類的實例方法,但是不能訪問父類的原型方法,因此無法做到函數複用
  • 優點:每個子類實例的屬性獨立存在

1.3 組合繼承

方法:同時使用原型繼承和構造繼承,綜合兩者的優勢所在

原理:通過原型繼承實現原型屬性和原型方法的繼承,通過構造繼承實現實例屬性和實例方法的繼承

function SuperType(name, info) {
    // 實例屬性(基本類型)
    this.name = name || 'Super'
    // 實例屬性(引用類型)
    this.info = info || ['Super']
    // 實例方法
    this.getName = function() { return this.name }
}
// 原型方法
SuperType.prototype.getInfo = function() { return this.info }

// 組合繼承
function ChildType(name, info, message) {
    SuperType.call(this, name, info)
    this.message = message
}
ChildType.prototype = new SuperType()
ChildType.prototype.constructor = ChildType

// 在調用子類構造函數時,可以向父類構造函數傳遞參數
var child = new ChildType('Child', ['Child'], 'Hello')

// 子類實例可以訪問父類的實例方法和原型方法
console.log(child.getName()) // Child
console.log(child.getInfo()) // ["Child"]

// 每個子類實例的屬性獨立存在
var other = new ChildType('Child', ['Child'], 'Hi')
other.info.push('Temp')
console.log(other.info) // ["Child", "Temp"]
console.log(child.info) // ["Child"]
  • 優點:在調用子類構造函數時,可以向父類構造函數傳遞參數
  • 優點:子類實例可以訪問父類的實例方法和原型方法
  • 優點:每個子類實例的屬性獨立存在
  • 缺點:在實現組合繼承時,需要調用兩次父類構造函數

2.1 原型式繼承

方法:實現一個函數,傳入已有對象,在函數內部將新對象的原型指向原有對象,最後返回新對象

原理:返回的新對象繼承原有對象,然後根據需求對得到的對象加以修改即可

var superObject = {
    name: 'Super',
    info: ['Super'],
    getName: function() { return this.name }
}

// 原型式繼承
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}

// 創建子類實例必須基於一個已有對象
var childObject = object(superObject)

// 根據需求對得到的對象加以修改
childObject.message = 'Hello'

// 新創建的實例可以訪問已有對象的實例屬性和實例方法
console.log(childObject.name) // Super
console.log(childObject.getName()) // Super

// 所有新創建的實例共享已有對象的引用屬性
var otherObject = object(superObject)
otherObject.info.push('Temp')
console.log(otherObject.info) // ["Child", "Temp"]
console.log(childObject.info) // ["Child", "Temp"]
  • 要求:創建子類實例必須基於一個已有對象

  • 缺點:所有新創建的實例都會重新定義已有對象的實例方法,因此無法做到函數複用

  • 缺點:所有新創建的實例共享已有對象的引用屬性

2.2 寄生式繼承

方法:創建一個用於封裝繼承過程的函數,在函數內部以某種方式增強對象,且最後返回對象

原理:基於原型式繼承,類似於工廠模式,將增強對象的過程封裝到一個函數中

var superObject = {
    name: 'Super',
    info: ['Super'],
    getName: function() { return this.name }
}

// 寄生式繼承
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
function objectFactory(o) {
    var clone = object(o)   // 創建對象
    clone.message = 'Hello' // 增強對象
    return clone            // 返回對象
}

// 創建子類實例必須基於一個已有對象
var childObject = objectFactory(superObject)

// 新創建的實例可以訪問已有對象的實例屬性和實例方法
console.log(childObject.name) // Super
console.log(childObject.getName()) // Super

// 所有新創建的實例共享已有對象的引用屬性
var otherObject = object(superObject)
otherObject.info.push('Temp')
console.log(otherObject.info) // ["Child", "Temp"]
console.log(childObject.info) // ["Child", "Temp"]
  • 要求:創建子類實例必須基於一個已有對象

  • 缺點:所有新創建的實例都會重新定義已有對象的實例方法,因此無法做到函數複用

  • 缺點:所有新創建的實例共享已有對象的引用屬性

3 寄生式組合繼承

方法:借用寄生式繼承的思路,結合組合繼承的方法,解決組合繼承中需要調用兩次父類構造函數的問題

原理:通過構造繼承實現實例屬性和實例方法的繼承,通過寄生式繼承實現原型屬性和原型方法的繼承

不用爲了指定子類的原型而調用父類的構造函數,而是使用寄生式繼承來繼承父類的原型,然後指定給子類的原型

function SuperType(name, info) {
    // 實例屬性(基本類型)
    this.name = name || 'Super'
    // 實例屬性(引用類型)
    this.info = info || ['Super']
    // 實例方法
    this.getName = function() { return this.name }
}
// 原型方法
SuperType.prototype.getInfo = function() { return this.info }

// 寄生式組合繼承
function object(o) {
    function F() {}
    F.prototype = o
    return new F()
}
function objectFactory(childType, superType) {
    var prototype = object(superType.prototype) // 創建對象
    prototype.constructor = childType           // 增強對象
    childType.prototype = prototype             // 將父類原型指定給子類原型
}
function ChildType(name, info, message) {
    SuperType.call(this, name, info)
    this.message = message
}
objectFactory(ChildType, SuperType)

寄生式組合繼承是 JavaScript 中最常用的繼承方式,ES6 中新增的 extends 底層也是基於寄生式組合繼承的

【 閱讀更多 JavaScript 系列文章,請看 JavaScript學習筆記

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章