[ES6 系列] 你應該知道的 Class

前言

本文是 ES6 系列的第四篇,能夠在 這裏 查看 往期全部內容git

這篇文章主要記錄了一些 class 相關的內容,都是咱們平常開發中可能會遇到的知識點es6

若是文章中有出現紕漏、錯誤之處,還請看到的小夥伴多多指教,先行謝過github

如下↓web

起源

ES6 以前,咱們生成實例對象的方法都是經過構造函數編程

function Person(name) {
    this.name = name
}
Person.prototype.say = function() {
    console.log(this.name)
}

var p = new Person('遊蕩de蝌蚪')

ES6 引入了 的概念,經過 class 關鍵字來定義。上面的代碼就能夠這樣改寫segmentfault

class Person {
    constructor(name) {
        this.name = name
    }
    say() {
        console.log(this.name)
    }
}

let p = new Person('遊蕩de蝌蚪')

class 的這種寫法更接近傳統語言,不管是對某個屬性設置存儲函數和取值函數,仍是實現繼承,都要更加清晰和方便微信

本質及特色

本質

類的本質是一個函數,類自身指向的就是構造函數
class Person {}

typeof Person // function

Person.prototype.constructor == Person // true

ES6class 能夠看做只是一個語法糖,它的絕大部分功能,ES5 均可以作到,新的 class 寫法只是讓對象原型的寫法更加清晰、更像面向對象編程的語法babel

類的構造方法 constructor 對應的就是構造函數,定義在類中的方法都定義在類的 prototype 屬性上面函數

class Person {
    constructor(){},
    say() {},
    run() {}
    ...
}
// 等同於
Person.prototype = {
    constructor(){},
    say(){},
    run(){}
    ...
}

let p = new Person()

p.say == Person.prototype.say // true

特色

  • 內部默認嚴格模式
  • 不存在變量提高
console.log(Person)
class Person {}
// Uncaught ReferenceError: 
// Cannot access 'Person' before initialization
  • 不能重複定義
let Person = {}
class Person {}
// Uncaught SyntaxError: 
// Identifier 'Person' has already been declared
  • constructor 是類的默認方法,就算不定義,也會默認添加一個空的 constructor
  • 實例必須使用 new 關鍵字初始化,不然會報錯
  • 類的全部實例共享同一個原型對象
class Person {}
let p1 = new Person()
let p2 = new Person()

p1.__proto__ == p2.__proto__ //true
  • 內部全部定義的方法,都是不可枚舉的,而且 prototype 默認不可重寫
class Person {}

Object.getOwnPropertyDescriptor(Person, 'prototype')
/* 
    configurable: false
    enumerable: false
    value: {constructor: ƒ}
    writable: false
*/

靜態方法

class Person {
    static say() {
        console.log('Hello')
    }
}

class 中若是一個方法前添加了 static 關鍵字,那麼就表示這個方法是靜態方法。它不能被實例繼承,只能經過類來調用學習

let p = new Person()
p.say() // Uncaught TypeError: p.say is not a function

Person.say() // Hello

須要注意的是:父類的靜態方法,能夠被子類繼承

class Tadpole extends Person {}

Tadpole.say() // Hello

靜態屬性

所謂靜態屬性,就是類自己的屬性,也就是說屬性不能經過添加在 this 上的方式定義

// 屬性不能定義在 this 上面,會被實例繼承
class Person {
    constructor(name) {
        this.name = name
    }
}
ES6 明確規定, Class 內部只有靜態方法,沒有靜態屬性

通常狀況下,咱們實現靜態屬性的方式就是直接將屬性添加到類上面:

Person.age = 18

可也以用一種變通的方式實現:

// 經過 static 靜態方法與 get 方式的結合
class Person {
    static get age() {
        return 18
    }
}

類的靜態屬性和靜態方法的表現基本一致:不能被實例繼承,只能經過類來使用

提案 提供了一種類靜態屬性的另外一種方式,也是使用 static 關鍵字

class Person {
    static name = 18
}

私有屬性

所謂私有,通常須要具有如下特徵:

  • 只能在 class 內部訪問,不能在外部使用
  • 不能被子類繼承

ES6 並無提供 class 的私有屬性及方法的實現方式,可是咱們能夠經過如下幾種方式來約定

  • 命名上面添加標識:方法名前面添加 _ ,可是這種方式不是很保險,由於在類的外部仍是能夠訪問到這個方法
class Person {
    // 公有方法
    fn(age) {
        this.age = age
    }
    // 私有方法
    _foo(age){
        return this.age = age
    }
}
  • 將私有的方法移出模塊,利用 this 創造出一個相對封閉的空間
class Person{
    foo(age) {
        bar.call(this, age)
    }
}
function bar(age) {
    return this.age = age
}

提案 提供了一種實現 class 私有屬性的方式:使用 # 關鍵字

class Person {
    #age = 18
}

若是咱們在外部使用這個屬性就會報錯

let p = new Person()
Person.#age
p.#age
// Uncaught SyntaxError: 
// Private field '#age' must be declared in an enclosing class

另外,私有屬性也支持 gettersetter 的方式以及靜態 static 的方式

class Person {
    #age = 18
    get #x() {
        return #age
    }
    set #x(value) {
        this.#age = value
    }
    static #say() {
        console.log(#age)
    }
}

繼承

class 出現以前,咱們通常都會使用原型以及構造函數的方式實現繼承,更多實現繼承的方式參考 JavaScript中的繼承

類的繼承也是經過原型實現的

ES5 的繼承,實質是先創造子類的實例對象 this ,而後再將父類的方法添加到 this 上面

ES6 的繼承,實質是先將父類實例對象的屬性和方法,加到 this 上面(因此必須先調用 super 方法),而後再用子類的構造函數修改 this

extends關鍵字

class 經過 extends 關鍵字實現繼承

經過 extends 關鍵字,子類將繼承父類的全部屬性和方法

class Person {}

class Tadpole extends Person {}

extends 關鍵字後面不只能夠跟類,也能夠是表達式

function Person(){
    return class {
        say(){
            alert('Hello')
        }
    }
}

class Tadpole extends Person(){}

new Tadpole().say() // Hello

extends 關鍵字後面還能夠跟任何具備 prototype 屬性的函數(這個特性可讓咱們很輕鬆的複製一個原生構造函數,好比 Object

function Fn() {
    this.name = 'tadpole'
}
// 注意,這裏 constructor 的指向變了
Fn.prototype = {
    say() {
        console.log('My name is tadpole')
    }
}

class Tadpole extends Fn {}

let t = new Tadpole()

t.name // tadpole
t.say() // My name is tadpole

super 關鍵字

子類經過繼承會獲取父類的全部屬性和方法,因此下面的寫法能夠獲得正確的結果

class Person {
    constructor() {
        this.name = '遊蕩de蝌蚪'
    }
}
class Tadpole extends Person{}

let t = new Tadpole()
t.name // 遊蕩de蝌蚪

可是,若是咱們在子類中定義了 constructor 屬性,結果就是報錯

class Tadpole extends Person {
    constructor(){}
}

let t = new Tadpole()
// Must call super constructor in derived class before accessing 'this' or returning from derived constructor

若是咱們想要在子類中定義 constructor 屬性,那麼就必須調用 super 方法

// 正常
class Tadpole extends Person {
    constructor(){
        super()
    }
}

let t = new Tadpole()

super表明了父類的構造函數,返回的是子類的實例,至關於 Person.prototype.constructor.call(this)

因此,上面代碼中 super() 的做用實際上就是將 this 添加到當前類,而且返回

super 有兩種使用方式:

做爲函數

super() 只能用在子類的構造函數之中,用在其餘地方就會報錯

做爲對象
  • 在普通方法中,指向父類的原型對象(super 指向父類的原型對象,因此定義在父類實例上的方法或屬性,沒法經過 super 調用)
class Person {
    constructor() {
        this.name = '遊蕩de蝌蚪'
    }
    say() {
        console.log('My name is' + this.name)
    }
}

class Tadpole extends Person {
    constructor() {
        super()
        console.log(super.say()) // My name is 遊蕩de蝌蚪
        console.log(super.name) // undefined
    }
}
  • 在靜態方法中,指向父類
class Person {
    constructor() {
        this.name = '遊蕩de蝌蚪'
    }
    say() {
        console.log('My name is' + this.name)
    }
    static say() {
        console.log('My name in tadpole')
    }
}

class Tadpole extends Person {
    static say() {
        super.say()
    }
    say() {
        super.say()
    }
}

Person.say() // My name is tadpole
Tadpole.say() // My name is tadpole
let t = new Tadpole()
t.say() // My name is 遊蕩de蝌蚪

class 中的this

  • 類的方法內部若是含有 this,默認指向類的實例
class Person {
    say() {
        this.run()
    }
    run() {
        console.log('Run!')
    }
}
  • 若是靜態方法包含 this 關鍵字,這個 this 指的是類,而不是實例
class Person {
    static say() {
        console.log(this) // Person
    }
}
  • 子類繼承父類,子類必須在 constructor 中調用 super() 以後才能使用 this
  • class 中默認使用嚴格模式,若是將其中的方法單獨調用,那麼方法中的 this 指向 undefined (默認指向全局對象)

class 的問題

class 的出現爲咱們的編程提供了不少便利,可是 class 自己也存在一些問題

  • 首先,增長了學習成本有木有。你說說我連原型、原型鏈還沒搞清楚呢,你又讓我去學 class,居然還只是 語法糖,你說氣人不氣人
  • 其次,基於 prototype,因此 class 也存在原型所具備的一些問題,好比修改父類上面的屬性可能會影響到全部子類(固然,私有屬性的出現仍是解決了一些問題)
  • 並非全部的環境都支持 class,好比那個啥啥啥,固然了也有解決的方式:babel
  • ...

儘管 class 仍是存在些許問題,但它必定會愈來愈豐富...

後記

以上就是關於 class 的所有內容,但願對看到的小夥伴有些許幫助

大膽地在你的項目中使用 class 吧,相信你絕對會愛上它

感興趣的小夥伴能夠 點擊這裏 ,也能夠掃描下方二維碼關注個人微信公衆號,查看往期更多內容,歡迎 star 關注

image

參考

ECMAScript 6 入門

MDN

相關文章
相關標籤/搜索