潛入理解ES6-類和繼承

noopyxu發表於2018-03-20

ES5中的類

在ES5宣告一個函式(可以new),再將方法新增到這個方法的原型上,來建立自定義型別。

function Person(name) {
    this.name = name;
}
Person.prototype.sayName = function() {
    console.log(this.name);
};
let person = new Person("xunuo0x");
person.sayName(); // 輸出 "xunuo0x"
console.log(person instanceof Person); // true
console.log(person instanceof Object); // true
複製程式碼

ES6中類的宣告

本質:ES5實現方式的語法糖

我們拿下面用class宣告的Person為例:也就是說Person類為一個具有建構函式行為的函式,其中內部方法sayName實際上就是Person.prototype.sayName()。所以說本質上是ES5實現方式的語法糖。

console.log(typeof Person) // 'function'
複製程式碼

區別在於,類的屬性不可重新賦值和不可列舉的,Person.prototype就是一個只讀屬性。

宣告一個類

宣告:

  • 構造器:構造器內建立自有屬性
  • 方法:宣告類例項具有的方法
class Person {
    // 等價於 Person 構造器
    constructor(name) {
    this.name = name;
    }
    // 更加簡單的宣告類的內部函式
    // 等價於 Person.prototype.sayName
    sayName() {
        console.log(this.name);
    }
}
let person = new Person("xunuo0x");
person.sayName(); // 輸出 "xunuo0x"
console.log(person instanceof Person); // true
console.log(person instanceof Object); // true
console.log(typeof Person); // "function"
console.log(typeof Person.prototype.sayName); // "function"
複製程式碼

class和自定義型別的區別

  • class的宣告不會提升,與let類似
  • class的宣告自動執行於嚴格模式之下
  • class宣告的方法不可列舉(顯著區別)
  • class的內部方法沒有[[construct]]屬性,無法new
  • 呼叫class的建構函式必須new
  • class內部方法不能同名

用ES5重寫如下: 在實現的時候,主要使用Object.defineProperty()實現class內部函式

// 直接等價於 Person
let Person2 = (function() {
    "use strict";
    // 有個同名的只讀內部函式
    // **類的內部不能修改類名**
    const Person2 = function(name) {
    // 確認函式被呼叫時使用了 new
    if (typeof new.target === "undefined") {
        throw new Error("Constructor must be called with new.");
    }
    this.name = name;
    }
   
    Object.defineProperty(Person2.prototype, "sayName", {
            value: function() {
            // 確認函式被呼叫時沒有使用 new
            if (typeof new.target !== "undefined") {
                throw new Error("Method cannot be called with new.");
            }
            console.log(this.name);
        },
         // **類的內部方法定義為不可列舉**
        enumerable: false,
        writable: true,
        configurable: true
    });
    return Person2;
}());
複製程式碼

類表示式

  • 匿名類表示式let Person = class{...}
  • 具名類表示式let Person = PersonClass class{...}
  • 區別僅在於class的內部實現時,const PersonClass作為內部實現的類名

class作為一級公民

js中能當作值來使用的稱為一級公民

用法:

  • 類名作為引數傳入函式
  • 立即執行,實現單例模式
// 類名作為引數傳入函式
function crateObj (ClassName){
    return new ClassName()
}
// 立即執行,實現單例模式
let person = new class {
    constructor (name) {
        this.name = name
    }
    say () {
        console.log(this.name)
    }
}('xunuo0x')
person.say() // "xunuo0x"
複製程式碼

class中訪問器屬性

  • get 關鍵字
  • set 關鍵字
  • 內部實現時將getter/setter變數名,通過Object.defineProperty()定義

class中靜態成員

  • static關鍵字
  • 相當於ES5中Person.create() = function() {}
  • 訪問時直接通過類訪問,不能通過例項訪問

使用extends繼承

只要一個表示式能返回具有[[constructor]]就可以使用extends繼承;也就是說可以繼承一個函式

ES5中的繼承

function Parent (name) {
    this.name = name
}
Parent.prototype.sayName = function () {
    console.log(this.name)
}
function Child (name) {
    Parent.call(this, name)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
複製程式碼

在ES6中的繼承

class Parent (name) {
    constructor (name) {
        this.name = name
    }
    sayName() {
        console.log(this.name)
    }
}
class Child extends Parent (name) {
    constructor(name) {
        super(name)
    } 
    // 重寫父類中的方法
    sayName () {
        console.log(`Child ${this.name}`)
    }
}
複製程式碼

繼承內建物件

  • ES5中繼承內建物件(如繼承Array可能會產生問題)
  • ES5中繼承,this先被派生類建立
  • ES6中繼承,this先被基類建立,就具有了基類的方法和屬性

Symbol.species屬性

  • extends繼承時,派生類上返回的是派生類的例項
  • 如果想返回基類,可以設定Symbol.species
class MyClass extends Array {
    static get [Symbol.species]() {
        return this; // 預設返回MyClass型別
        return Array; // 修改返回基類
    }
    constructor(value) {
        this.value = value;
    }
}
複製程式碼

new.target

  • 見名知意,就是new操作執行的物件
  • ES6中例項化class時,必須要new,所以在constructor()new.target不可能是undefined

mixin繼承

function mixin (...mixin) {
    var base = function() {}
    Object.assign(base, ...mixin)
    return mixin
}

class Person extends mixin(Animal, Monkey) {
    constructor(){
        super(Animal, Monkey)
        // ......
    }
}

複製程式碼

小結

  • ES6中class簡化了ES5中的繼承,但是未改變現有的繼承模型。可以理解為是ES5基於原型鏈的語法糖
  • 通過class宣告一個類,constructor()作為建構函式,屬性在constructor()中初始化
  • class內可以定義getter/setter訪問器屬性
  • 可以在class內定義非靜態方法,靜態方法繫結在構造器上
  • 類的所有方法都是不可列舉的,也符合內部方法
  • 例項化一個class必須要new關鍵字
  • extends實現繼承,子類中呼叫super()訪問父類建構函式
  • 因為class的實現是基於ES5類模型那一套,本質上和ES5中是一樣的,如果過多使用extends可能還會降低效能

相關文章