深入理解JavaScript中的類繼承

ChuJQ發表於2021-02-14

由於寫本文時全部是在編輯器中邊寫程式碼邊寫感想的,所以,全部思想都寫在程式碼註釋裡面了

// 類繼承

//todo.1 extends 關鍵字

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  run(speed) {
    this.speed = speed;
    console.log(`${this.name} runs with speed ${this.speed}`);
  }
}

// 如果“派生類”使用constructor函式,則必須在constructor呼叫this之前使用super來呼叫被繼承類的constructor
// 如果“派生類”沒有使用constructor函式,則預設會生成一個constructor,程式碼如下
/**
 * constructor(...args) {
 *     super(...args)
 * }
 */
// 為什麼需要super() ?
// 因為“派生類(derived constructor)的建構函式與其他函式之間的區別在於其具有特殊的內部屬性[[ConstructorKind]]:derived”
// 這個屬性會影響new 的行為; 當通過new執行一個常規函式時,它將建立一個空物件,並將這個空物件賦值給this;
// 但是當繼承的constructor執行時,它不會執行此操作,它期望父類的constructor來完成這項工作。因此派生類必須執行super才能執行
// 父類的constructor來完成這項工作,否則this指向的那個物件不會建立,並且程式會報錯!
class Rabbit extends Animal {
  constructor(name, color) {
    super(name);
    this.color = color;
  }
}

const rabbit = new Rabbit("兔子", "白色");


//todo.2 深入探究內部原理和[[HomeObject]]
// 讓我們先來看一個例子。

const animal = {
    name:'Animal',
    eat() {
        console.log('animal');
    }
}

const tiger = {
    __proto__:animal,
    name:'tiger',
    eat() {
        this.__proto__.eat.call(this);
    }
}

const youngTiger = {
    __proto__:tiger,
    name:'youngTiger',
    eat() {
        this.__proto__.eat.call(this);
    }
}


tiger.eat(); // animal
// youngTiger.eat(); // RangeError: Maximum call stack size exceeded

// 為什麼會報錯?讓我們來深入探究一下

/**
 * 在youngerTiger.eat中
 * this.__proto__.eat.call(this)
 * 等於
 * youngTiger.__proto__.eat.call(this)
 * 等於
 * tiger.eat.call(this)
 * 在tiger.eat中
 * this.__proto__.eat.call(this)
 * 等於
 * youngTiger.__proto__.eat.call(this)
 * 等於
 * tiger.eat.call(this)
 */

// 解決方案:[[HomeObject]]

// 當一個函式被定義為類或者物件方法時,它的 [[HomeObject]] 屬性就成為了該物件。
// 然後 super 使用它來解析(resolve)父原型及其方法。

let plant = {
    name:'Plant',
    grow() {
        console.log(`${this.name} growing`);
    }
}

let flower = {
    __proto__:plant,
    grow() {
        super.grow();
    }
}

let greenFlower = {
    __proto__:flower,
    grow() {
        super.grow()
    }
}

greenFlower.grow();//Plant growing

// [[HomeObject]]內建屬性是被繫結到JavaScript物件上的,“方法”由哪個物件建立,則
// [[HomeObject]]指向哪個物件,並且[[HomeObject]]一旦建立就是不可變的
// 而且只能被super解析。注意:“方法”不是“函式屬性”,
// “方法” {method(){}},“函式屬性”{method:function(){}}

// 解析,當物件巢狀繼承時,為了避免Maximum call stack size exceeded錯誤,
// 我們可以使用super來代替 this.__proto__.method.call(this)方法,前提是
// [[HomeObject]]屬性存在,並且__proto__存在

推薦閱讀:《現代JavaScript教程》- 類繼承

相關文章