es6 快速入門 系列 —— 類 (class)

其餘章節請看:javascript

es6 快速入門 系列html

類(class)是 javascript 新特性的一個重要組成部分,這一特性提供了一種更簡潔的語法和更好的功能,可讓你經過一個安全、一致的方式來自定義對象類型。java

試圖解決的問題

es5 及早期版本中沒有類的概念,一般會編寫相似下面這樣的代碼來自定義類es6

// 自定義類的思路是:首先建立一個構造函數,而後定義一個方法並賦值給構造函數的原型
function Rectangle(length, width){
    this.length = length;
    this.width = width;
}
Rectangle.prototype.getArea = function(){
    return this.length * this.width
}
let rect = new Rectangle(2, 3)
console.log(rect.getArea()) // 6
console.log(rect instanceof Rectangle) // true

若是還須要實現繼承,可能會這麼實現:數組

// 定義類:Rectangle
function Rectangle(length, width){
    this.length = length;
    this.width = width;
}
Rectangle.prototype.getArea = function(){
    return this.length * this.width
}
// 定義類:Square
function Square(length){
    // 對象冒充
    Rectangle.call(this, length, length)
}
// 指定 Square 的原型
Square.prototype = Object.create(Rectangle.prototype, {
    constructor: {
        value: Square,
        enumerable: true,
        writable: true,
        configurable: true
    }
});

let square = new Square(3)

console.log(square.getArea()) // 9 - getArea() 方法從父類繼承
console.log(square instanceof Square) // true
console.log(square instanceof Rectangle) // true

經過本身模擬類、模擬繼承,實現起來比較複雜,也很是容易出錯。安全

解決方法

es6引入類(class),讓類的建立和繼承都更加容易。下面咱們重寫 Rectangle 的例子:app

// 經過 class 定義一個類
class Rectangle{
    constructor(length, width){
        this.length = length;
        this.width = width;
    }
    // 方法直接寫在 class 中
    getArea(){
        return this.length * this.width
    }
}
// 經過 extends 關鍵字指定類繼承的函數,原型會自動調整
class Square extends Rectangle{
    constructor(length){
        // 經過調用 super() 方法便可訪問基類的構造函數
        super(length, length)
    }
}

let square = new Square(3)

console.log(square.getArea()) // 9
console.log(square instanceof Square) // true
console.log(square instanceof Rectangle) // true

這個版本比咱們自定義的要簡單不少,若是你先前對其餘語言中的繼承語法有所瞭解,那麼你應該會以爲很親切。函數

有關類(class)的具體使用、其餘特性,均可以在下面的補充章節中找到答案this

補充

類的建立

類和函數都存在聲明形式和表達式形式。es5

聲明類,首先編寫 class 關鍵字,接着是類的名字,其餘部分的語法相似對象字面量的簡寫形式,但類的各元素之間不須要使用逗號。就像這樣:

// class 類名
class People{
    constructor(name){
        this.name =name;
    }
    // 不須要逗號
    sayName(){
        console.log(this.name)
    }
}
let people = new People('aaron')
people.sayName() // aaron
console.log(typeof People) // function {1} - People 其實仍是一個函數

類的表達式語法:

// 直接將一個類賦值給變量 People
let People = class{
    constructor(name){
        this.name =name;
    }
    sayName(){
        console.log(this.name)
    }
}
let people = new People('aaron')
people.sayName() // aaron

類(class)只是自定義類的一個語法糖。用 class 聲明的類是一個函數,那麼將這個函數賦值給一個變量,固然沒問題。

類是一等公民

在程序中,一等公民是指能夠傳入函數,能夠從函數返回,也能夠賦值給變量。javascript 函數是一等公民,這是 javascript 中的一個獨特之處,es6 也把類(class)設計成了一等公民。例如,將類(class)做爲參數傳入函數:

function createObject(classDef){
    return new classDef()
}
let obj = createObject(class People{
    sayName(){
        console.log('aaron')
    }
})
obj.sayName() // aaron

繼承

在」解決方法「章節中,咱們已經學會用 class 建立類,經過 extends 關鍵字指定類繼承的函數,以及使用 super() 調用基類的構造函數。

當使用 super() 時切記如下幾個關鍵點

  • 只能夠在派生類(使用 extends 聲明的類)的構造函數中使用 super(),不然會致使程序拋出錯誤
  • 在構造函數中訪問 this 以前,必定要調用 super(),它負責初始化 this,若是在調用 super() 以前訪問 this 會致使程序報錯
  • 若是不想調用 super(),惟一的方法是讓構造函數返回一個對象,不然也會致使程序報錯
class A{}
class B extends A{
    constructor(){
       
    }
}
let b = new B() // 報錯 - 沒有調用super()

內建對象的繼承

如今能夠經過繼承建立本身的特殊數組。請看下面:

class MyArray extends Array{

}
let myArray = new MyArray()
myArray[0] = 11
console.log(myArray.length) // 1

myArray.length = 0;
console.log(myArray[0]) // undefined

MyArray 繼承自 Array,其行爲與 Array 也很類似。但在之前,用傳統的繼承方式是沒法實現這樣的功能。es6 類語法的一個目標是支持內建對象繼承,於是 es6 中的類繼承模型與 es5 中的稍有不一樣,主要體現是:

  • es5 先由派生類(例如,MyArray)建立 this,而後調用基類的構造方法(例如 Array.apply()方法)
  • es6 則相反,先由基類建立this,而後派生類的構造函數再修改,因此從一開始能夠訪問基類的全部內建功能

咱們也能夠繼承自其餘內建對象

靜態成員

es5 及更早,經過直接將方法添加到構造函數中來模擬靜態成員,例如:

function People(){}

People.create = function(){}

es6 簡化了這個過程,類(class)經過 static 關鍵字定義靜態方法:

class People{
    constructor(name){
        this.name = name
    }
    static create(name){
        return new People(name)
    }
}
let p1 = People.create('aaron')

console.log(p1.name) // aaron

靜態成員也能夠被繼承,請看示例:

class A{
    static say(){
        console.log('A say')
    }
}
class B extends A{
 
}
B.say() // A say

派生自表達式的類

es6 中最強大的一面或許是從表達式中導出類了。只要表達式能夠被解析爲一個函數而且具備[[Constructor]]屬性和原型,就能夠用 extends 派生。舉個例子:

function Rectangle(length, width){
    this.length = length;
    this.width = width;
}
Rectangle.prototype.getArea = function(){
    return this.length * this.width
}

function getBase(){
    return Rectangle;
}
// getBase()這個表達式返回 Reactangle, 具備[[Constructor]]屬性和原型,所以 Square 類能夠直接繼承它
class Square extends getBase(){
    constructor(length){
        super(length, length)
    }
}

let square = new Square(3)
console.log(square.getArea()) // 9

extends 強大的功能使得類能夠繼承自任意類型的表達式,從而建立更多的可能性。

類的構造函數中使用 new.target

class A{
    constructor(){
        if(new.target === A){
            console.log(true)
        }else{
            console.log(false)
        }
    }
}

class B extends A{
    constructor(){
        super()
    }
}

new A() // true {1}
new B() // false {2} - B 調用 A 的構造函數,因此當調用發生時 new.target 等於 B

簡單狀況下,new.target 等於類的構造函數,就像行{1},但有時卻會不一樣(行{2})。理解它(行{2})很是重要,由於每一個構造函數能夠根據自身被調用的方式改變本身的行爲。例如,能夠用 new.target 建立一個抽象基類(不能被直接實例化的類),就像這樣:

// 抽象基類 BaseClass
class BaseClass{
    constructor(){
        if(new.target === BaseClass){
            throw new Error('這個類不能被實例化')
        }
    }
    sayName(){
        console.log(this.name)
    }
}
class B extends BaseClass{
    constructor(name){
        super()
        this.name = name
    }
}
let b = new B('aaron')
b.sayName() // aaron
new BaseClass() // Error: 這個類不能被實例化

訪問器屬性和可計算成員名稱

類支持直接在原型上定義訪問器屬性,例如咱們用訪問器屬性封裝一下數據 name:

// 數據 name 存儲在變量 _name 中,只能經過訪問器屬性來操做,從而達到數據封裝的目的
class People{
    constructor(name){
        this._name = name;
    }
    set name(v){
        this._name = v;
    }
    get name(){
        return this._name
    }
}

let people = new People('aaron')
console.log(people.name) // aaron
people.name = 'lj'
console.log(people.name) // lj

類方法和訪問器屬性也支持可計算成員名稱,請看下面示例:

let methodName = 'sayName'
let getName = 'age'
class People{
    constructor(name){
        this.name = name;
    }
    [methodName](){
        console.log(this.name)
    }
    get [getName](){
        console.log('get')
    }
}
let people = new People('aaron')

people.sayName() // aaron
people.age // get

生成器

在類中,能夠將任意方法定義成生成器。但若是你的類是用來表示集合,那麼定義一個默認的迭代器會更有用:

class Collection{
    constructor(){
        this.items = []
    }
    push(v){
        this.items.push(v)
    }
    *[Symbol.iterator](){
        yield *this.items.values()
    }
}
let collection = new Collection()
collection.push('11')
collection.push('22')
collection.push('33')

for(let v of collection){
    console.log(v) // 11 22 33
}

其餘章節請看:

es6 快速入門 系列

相關文章
相關標籤/搜索