深刻理解JavaScript中的類繼承

因爲寫本文時所有是在編輯器中邊寫代碼邊寫感想的,因此,所有思想都寫在代碼註釋裏面了javascript

// 類繼承

//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教程》- 類繼承java

相關文章
相關標籤/搜索