[打牢基礎系列]JavaScript的類與繼承

什麼是繼承

  1. 繼承的概念

繼承簡單來講就是使子類擁有父類的屬性和方法,而不須要重複編寫相同的代碼.前端

舉個例子,飛機有客運機和戰鬥機,這兩種機型的共有屬性是顏色,公有方法是起飛,可是客運機的承載的是乘客,而戰鬥機承載的是子彈,它還有一個功能是射擊.咱們就能夠建立一個父類,具備屬性顏色和方法起飛,這樣客運及和戰鬥機這兩個子類就能夠繼承該父類,而後在父類的基礎上擴展本身的屬性和方法.vue

  1. 原型

繼承要靠原型來實現,咱們先來了解一下原型的概念.web

  • 全部對象都有一個屬性 proto 指向一個對象,也就是原型
  • 每一個對象的原型均可以經過 constructor 找到構造函數,構造函數也能夠經過 prototype 找到原型
  • 全部函數均可以經過 proto 找到 Function 對象
  • 全部對象均可以經過 proto 找到 Object 對象
  • 對象之間經過 proto 鏈接起來,這樣稱之爲原型鏈。當前對象上不存在的屬性能夠經過原型鏈一層層往上查找,直到頂層 Object 對象

實現繼承的方式

查找一個對象的屬性或者方法,首先會在該對象上查找,找不到會沿着原型鏈向上查找,所以實現繼承也就是使得一些方法和屬性在該對象的原型鏈上,這樣該對象也就具備了這些屬性和方法.瀏覽器

原型鏈繼承

原型鏈繼承的實現

原型鏈的繼承即建立構造函數的多個實例,從而這多個實例就擁有了構造函數的屬性和方法.示例代碼:異步

// 片斷A
function Plane(color) {
    this.color = color
}
Plane.prototype.fly = function() {
    console.log('flying')
}
// Fighter 構造函數
function Fighter() {
    this.bullets = []
}
// 繼承Plane構造函數的屬性和方法
Fighter.prototype = new Plane('blue')
// 特有方法
Fighter.prototype.shoot = function() {
    console.log('biu biu biu')
}
var fighter1 = new Fighter()
console.log(fighter1.color)   // blue
fighter1.fly()    // flying
複製代碼

這樣fighter1就具備了父類Plane的color屬性和fly方法,並在此基礎上擴展了本身的shoot方法.函數

原型鏈繼承的不足

  • constructor指向問題 代碼片斷A的寫法會致使一個constructor指向的問題.工具

    // 正常狀況下
      function A(color) {
          this.color = color
      }
      var a = new A()
      a.__proto__ = A.prototype
      a.constructor = A.prototype.constructor = A
      // 片斷A的寫法
      filter.__proto__  = Filter.prototype
      filter.constructor = Filter.prototype.constructor
      // 因爲Filter.prototype = new Plane(),咱們手動更改了Filter的prototype屬性
      // 因此有
      Filter.prototype.constructor = Plane
      filter.constructor = Plane
      // 爲了解決上述問題,咱們須要對片斷A的代碼作如下調整
      Fighter.prototype. = new Plane()
      Fighter.prototype.constructo = Fighter
      console.log(filter.constructor)     // Filter
    複製代碼
  • 屬性共享問題post

多個實例共享父類構造函數的屬性,若是共享的屬性是引用類型,會致使其中一個示例最這些屬性作了更改影響到其餘實例中對該屬性的使用.屬性共享會致使數據污染,代碼的可維護性下降.優化

  • 參數

如代碼片斷A這樣,Fighter構造函數建立的實例的繼承得來的color屬性都是blue,實例的顏色最好統一是blue,否則後面一個個修改color,代碼的複用性的下降了.this

構造函數繼承

構造函數繼承的實現

// 片斷B
    function Plane(color) {
        this.color = color
    }
    Plane.prototype.fly = function() {
        console.log('flying')  
    }
    function Fighter(color, content) {
        Plane.call(this, color) 
        this.content = content
    }
    Fighter.prototype.shoot = function() {
        console.log('biu biu biu')
    }
    var flighter1 = new Fighter('blue', 'zidan');
    console.log(flighter1.color);  // blue
    console.log(flighter1.content); // zidan
    flighter1.shoot(); // 'biu biu biu';
    flighter1.fly();   //  Uncaught TypeError: flighter1.fly is not a function
    at <anonymous>:19:15
複製代碼

構造函數繼承的不足

構造函數繼承的不足就片斷B中既能夠看出,它只能實現繼承構造函數自己的屬性和方法,而不能繼承構造函數原型上的屬性和方法.

組合繼承

組合繼承的代碼實現

組合繼承, 顧名思義是原型鏈繼承和構造函數函數繼承的組合.父類的屬性經過構造函數繼承爲私有屬性,父類的方法經過原型鏈繼承,彌補了原型鏈繼承和構造函數繼承的不足

function Plane(color) {
    this.color = color
}
Plane.prototype.fly = function() {
    console.log('flying')
}
function Fighter(color) {
    Plane.call(this, color)
    this.bulltes = []
}
Fighter.prototype = new Plane()
Fighter.prototype.constructor = Fighter
Fighter.prototype.shoot = function() {
    console.log('biu biu biu')
}
複製代碼

組合繼承的優勢

  • 屬性和方法都是從父類繼承的(代碼複用)
  • 繼承的屬性是私有的(互不影響)
  • 繼承的方法都在原型裏(代碼複用)

組合繼承的不足

組合繼承的方法致使了咱們重複調用了構造函數Plane.且Plane構造函數內部的color不會被用到,致使了屬性冗餘.

最佳實踐(優化版的組合繼承)

主要原理

  • 基於組合繼承
  • 避免重複調用父類構造函數,只需繼承原型

組合繼承的代碼實現(優化版, 也有說法叫寄生組合繼承)

// 片斷C
    function Plane(color) {
        this.color = color
        this.pilots = []
    }
    Plane.prototype.fly = function() {
        console.log('flying')
    }
    // Fighter 構造函數
    function Fighter(color) {
        Plane.call(this, color)
        this.bullets = []
    }
    // 繼承Plane構造函數的屬性和方法
    inheritPrototype(Fighter, Plane)
    // 特有方法
    Fighter.prototype.shoot = function() {
        console.log('biu biu biu')
    }
    var fighter1 = new Fighter()
    console.log(fighter1)
    // inheritPrototype的第一種實現方式
    function inheritPrototype(child, parent) {
        var proto = Object.create(parent.prototype)
        proto.constructor = child
        child.prototype = proto
    }
    // inheritPrototype的第二種實現方式
    function inheritPrototype(child, parent) {
        var proto = Object.create(parent.prototype)
        child.prototype = proto
        child.prototype.constructor = child
    }
    // inheritPrototype的第三種實現方式
    function inheritPrototype(child, parent) {
        var proto = function() {}
        proto.prototype = parent.prototype
        child.prototype = new proto()
        child.prototype.constructor = child
    }   
複製代碼

ES6的繼承

ES6的繼承代碼實現:

咱們都知道,目前的js的繼承的實現比較繁瑣, 要調用構造函數,又要本身封裝繼承原型的函數,因此ES6標準將class聲明類和繼承類的方式歸入標準,示例代碼以下:

// 片斷D
class Plane1 {
    constructor(color) {
        this.color = color
    }
    fly() {
        console.log('flying')
    }
}
// 使用extends關鍵字實現繼承
class Fighter1 extends Plane1 {
    constructor(color, content) {
        super(color)
        this.content = content
    }
    shoot() {
        console.log('biu biu biu')
    }
}
const fight1 = new Fighter1('blue', 'zidan')
fight1.color      // blue
fight1.content    // zidan
fight1.fly()      // flying
fight1.shoot()    // biu biu biu
複製代碼

注意: 目前部分現代瀏覽器新版本已經實現對 ES6 中的class和繼承的,可是注意在舊版本或者 IE 瀏覽器中是不支持的,因此使用的時候要注意,或者配合使用 Babel 等編譯工具。

總結

啦啦啦~~~,寫完了,後續我會堅持一系列的前端知識的分享的,但願大家讀完個人文章後可以有所收穫~~~.聽明白和講明白之間仍是差了不少,若是我有表達不當的地方或者能夠優化的地方還請指出,但願努力的咱們都能成爲優秀的本身~~~

擴展閱讀

相關文章
相關標籤/搜索