JavaScript夯實基礎系列(五):類

白馬笑西風發表於2019-03-18

  JavaScript中沒有類,是通過使用建構函式和原型模式的組合來實現類似其它物件導向程式語言中“類”的功能。ES6引入的關鍵字class,形式上向其它物件導向程式語言靠攏,其實質只是一個語法糖,絕大部分功能ES5都可以實現。

一、class

  在ES6之前,建立自定義物件最常用的方式是組合使用建構函式和原型模式。而ES6通過class關鍵字在形式上加以規範。

// ES6之前的寫法
function OldStudent (name,age) {
    this.name = name
    this.age = age
}
OldStudent.prototype.sayMessage = function () {
    console.log(`我叫${this.name},今年${this.age}歲。`)
}

// ES6寫法
class NewStudent{
    constructor (name, age) {
        this.name = name
        this.age = age
    }

    sayMessage() {
        console.log(`我叫${this.name},今年${this.age}歲。`)
    }
}

let LiLei = new OldStudent('LiLei',20)
LiLei.sayMessage() // 我叫LiLei,今年20歲。

let HanMeiMei = new NewStudent('HanMeiMei',18)
HanMeiMei.sayMessage() // 我叫HanMeiMei,今年18歲。

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

  由上程式碼可以看出,通過class關鍵字定義的程式碼塊也是函式。比較特殊的是通過class定義的函式只能通過new關鍵字來呼叫,並且不存在變數提升,只能先定義後使用。另外,class定義的函式內部模式是嚴格模式。
  class中的constructor方法相當於ES5中的建構函式,在例項化物件時必須呼叫constructor方法,如果沒有顯式定義,引擎也會自動新增一個空的constructor方法。constructor方法和建構函式生成物件的規則一樣:建立一個新物件,作用方法的執行上下文,執行方法中的程式碼;如果沒有明確return一個物件,則返回新建立的物件。
  class中除了定義constructor方法之外的方法是直接定義在建構函式的原型物件上的。如果該方法前面加了static關鍵字,則該方法為靜態方法,直接定義在建構函式上而不是原型物件上。ES6明確規定class中可以定義靜態方法,無法在其中定義靜態變數。想要新增靜態變數,可以在class之外直接向函式上新增。如下程式碼所示:

// sayHello為靜態方法
class NewStudent{
    static sayHello() {
        console.log('hello')
    }
}

// 新增靜態屬性
NewStudent.type = 'student'

NewStudent.sayHello() // hello
console.log(NewStudent.type) // student
複製程式碼

  注意:通過class關鍵字向原型物件上只能新增方法而不能新增屬性。也許是為了防止在原型物件上新增物件的情況,這樣會導致在繼承的時候子物件可以修改原型鏈上物件的資料。但是這樣有些矯枉過正了,JavaScript的很大優勢在於靈活性,class關鍵字定義類,外形規範的同時降低了靈活性。
  為了實現資料共享,可以在通過class定義過類之後,再向原型物件中新增屬性。或者在class中定義getter方法,這樣的話就不能在子物件上直接新增同名屬性,要通過Object.defineProperty()方法來定義子物件上的同名屬性。如下程式碼所示:

class Person{
    get age () {
        return 18
    }
}

class Student extends Person{
    constructor (){
        super()
    }
}

let LiLei = new Student()
console.log(LiLei.age) // 18
LiLei.age = 20 // 非嚴格模式下賦值失敗,嚴格模式下報錯
console.log(LiLei.age) // 18

// 通過Object.defineProperty()方法修改
Object.defineProperty(LiLei, 'age', {
    value: 20
})
console.log(LiLei.age)  // 20
複製程式碼

  由上程式碼所示,ES6的原型屬性新增很麻煩,如果想要往原型物件上新增屬性,直接往類的prototype屬性賦值最為簡單。

二、extends

  實現引用型別繼承的最佳方式是寄生組合式繼承,ES6通過extends關鍵字來使這種繼承方式形式更加優雅,如下程式碼所示:

// ES6之前的繼承
function PersonA (name) {
    this.name = name
}

PersonA.prototype.sayName = function () {
    console.log(this.name)
}

function StudentA (name,age) {
    PersonA.call(this,name)
    this.age = age
}

StudentA.prototype = Object.create(PersonA.prototype)

// ES6的繼承
class PersonB{
    constructor (name){
        this.name = name
    }
    sayName () {
        console.log(this.name)
    }
}

class StudentB extends PersonB{
    constructor (name, age) {
        super(name)
        this.age = age
    }
}

let LiLei = new StudentA('LiLei',20)
LiLei.sayName() // LiLei
let HanMeiMei = new StudentB('HanMeiMei',18)
HanMeiMei.sayName() // HanMeiMei
複製程式碼

  子類的constructor中必須呼叫super方法,在執行super()之前子類不能使用this,這是因為ES6之前的寄生組合式繼承是先建立子類的例項,然後通過call或者apply方法將該例項作為執行上下文執行一遍父類建構函式。而ES6正好相反,先建立父建構函式的例項物件,然後將例項物件作為this執行子建構函式。這就導致如果不呼叫super方法子類中就不能使用this。
  ES6繼承中先建立父類例項的特性還帶來一個ES5無法實現的功能:繼承原生建構函式。如下程式碼所示:

class MyArray extends Array{
    constructor (...args) {
        super(...args)
    }
}

let number = new MyArray(1,2,3)
console.log(number.length) // 3

number.push(4)
console.log(number.length) // 4
複製程式碼

  通過extends子類同時能夠繼承父類的靜態方法。如下程式碼所示:

class Person{
    constructor (){}
    static sayHello () {
        console.log('hello')
    }
}
      
class Student extends Person{
    constructor () {
        super()
    }
}

Student.sayHello() // hello
複製程式碼

  super可以作為方法在子類建構函式中呼叫,也可以作為物件使用。當super作為物件時,在普通方法中指向父類的原型物件,在靜態方法中,指向父類。另外,super在普通方法中指向的是父類的原型物件,無法通過super來訪問父類例項中的屬性和方法。如下程式碼所示:

class Person{
    constructor (name){
        this.name = name
    }

    static sayStatic () {
        console.log('static')
    }
}

class Student extends Person{
    constructor (name) {
        super(name)
    }

    static sayStatic () {
        super.sayStatic()
    }

    sayName () {
        console.log(super.name)
    }
}

let LiLei = new Student('LiLei')
Student.sayStatic() // static
LiLei.sayName() // undefined
複製程式碼

三、總結

  ES6新增的關鍵字class、extends在形式上規範了JavaScript對類的模擬,使程式碼看起來更加優雅。而且能夠通過extends來擴充套件內建的物件型別,比如Array或者RegExp。
  class帶來的也不全是好處,將.prototype隱藏起來,使人更加迷惑。實際上JavaScript是沒有類的,通過原型鏈來實現繼承更恰當的說法應該是委託。而且僅僅通過class關鍵定義建構函式和原型物件的混合體靈活性有所降低,比如:在原型物件上只能新增方法,無法新增屬性。
如需轉載,煩請註明出處:www.cnblogs.com/lidengfeng/…

相關文章