【深入淺出ES6】Class

Eric_zhang發表於2019-03-04

【深入淺出ES6】Class

背景

在ES6之前,官方都不支援類的語法,很多第三方類庫都通過模擬實現了類似類的用法,最終ES6引入了類

類的介紹

類的宣告

ES6之前的寫法:稱為自定義型別
function Person(name){
    this.name = name
}
Person.prototype.sayName = function(){
    return this.name
}
複製程式碼
ES6的寫法:
class Person {
    //構造器
    constructor(name){
        this.name = name //自有屬性
    }
    sayName(){
        return this.name
    }
}
let person = new Person('Tom')
console.log(person.sayName()) //Tom
console.log(person instanceof Person) //true
console.log(person instanceof Object) //true

console.log(typeof Person) //function
console.log(typeof Person.sayName) //undefined
console.log(typeof Person.prototype.sayName) //function
複製程式碼
  • console.log(typeof Person) //function 通過列印結果可以看到,ES6對類的宣告類似ES5自定義型別寫法的語法糖,本質上class定義的Person仍然是一個函式
  • console.log(typeof Person.sayName) //undefined console.log(typeof Person.prototype.sayName) //function 通過列印結果可以看到,ES6類中宣告的方法實際也是掛載到原型上的,這個ES5自定義型別中定義的方法是一樣的

類表示式:

let Person = class {
    //構造器
    constructor(name){
        this.name = name //自有屬性
    }
    sayName(){
        return this.name
    }
}

//具名類表示式
let Person = class Person2{
    //構造器
    constructor(name){
        this.name = name //自有屬性
    }
    sayName(){
        return this.name
    }
}
**這裡的Person2只能在類的方法中被訪問到**
複製程式碼
使用ES6宣告類的優勢
  1. 類宣告不會被提升,類宣告類似於let,在程式執行到達類宣告之前,是處於暫時性死區中的,訪問會報錯。ES5中的函式定義法,函式是可以被提升到當前作用域頂部的
  2. 類宣告中的程式碼強制在嚴格模式下執行'use strict',且不能退出
  3. 類的所有方法不可列舉,ES5的自定義型別只能通過Object.defineProperty()來定義屬性描述符規定其不可列舉
  4. 呼叫類生成例項時,必須使用new關鍵詞,會自動呼叫類中的構造器constructor函式。不適用new 呼叫類會報錯;這是自定義型別的函式是沒辦法規避的
  5. 類的方法不能使用new 關鍵詞呼叫,會丟擲錯誤
  6. 試圖在類的方法中重寫類名(相當於const類名),會丟擲錯誤;在類外部可以重寫類名(相當於let類名)

類是一等公民

在程式設計中,能被當做值來使用的就稱為一等公民,也就是說,就像函式那樣,能夠作為函式的引數、函式的返回值、能用來給變數賦值等操作

function createClass(classDef){
    return new classDef()
}
let obj = createClass(class {
    sayHi(){
        console.log('Hi')
    }
})

obj.sayHi() //Hi
複製程式碼

類的訪問器屬性

class Person {
    constructor(name){
        this.value = name
    }
    get name(){
        console.log('getter')
        return this.value
    }
    set name(name){
        console.log('setter')
        this.value = name
    }
}
let person = new Person('Tom')
console.log(person.name)
person.name = 'Lisa'
console.log(person.name)
/*
getter
Tom
setter
getter
Lisa
*/
複製程式碼

為類新增生成器方法(定義Symbol.iterator)

class Collection {
    constructor(){
        this.items= []
    }
    *[Symbol.iterator](){
        yield *this.items.values() //this.items.values()是陣列內建的迭代器,這裡相當於把this.items的生成器合併在類生成器中了
    }
}
let collection = new Collection()
collection.items.push(1)
collection.items.push(2)
collection.items.push(3)

for(let value of collection){
    console.log(value)
}
/*
1
2
3
*/
複製程式碼

靜態成員

我們之前已經實驗過,在類中定義的方法,是把此方法屬性加到類的原型上的,類的例項也都繼承了這些方法,可以訪問; 如果不希望例項可以訪問,只能被類自身訪問,則需要使用靜態成員

ES5傳統的實現寫法
function Person(name){
    this.name = name
}
//新增在原型上,所有例項都可以訪問
Person.prototype.getName = function(){
    return this.name
}
靜態方法,例項訪問不到
Person.sayHi = function(){
    console.log('Hi')
}
複製程式碼

ES6寫法

class Person {
    constructor(name){
        this.name = name
    }
    //新增到原型上,所有例項都可以訪問
    getName(){
       return this.name 
    }
    static sayHi(){
       console.log('Hi') 
    }
}
let person = new Person('Tom')
console.log(person.getName()) //Tom
// person.sayHi() // 丟擲異常 person.sayHi is not a function
Person.sayHi()//Hi
複製程式碼

使用extends關鍵詞完成類的繼承

class Rectangle {
    constructor(length, width){
        this.length = length;
        this.width = width;
    }
    getArea(){
        return this.length * this.width
    }
}
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
複製程式碼
  • 繼承父類的類叫派生類,派生類如果顯式定義了構造器,就必須在訪問this之前,要顯式地使用super()呼叫父類的構造器,完成父類初始化this,並通過繼承傳遞給派生類,否則在派生類中訪問this時會報錯
  • 在派生類中才可以使用super關鍵詞,指代父類,super()表示先初始化父類的構造器,然後派生類繼承父類的this,所以在派生類中this也就可以訪問了
    class Rectangle {
      constructor(){
      	console.log('call parent')
          this.length = 10
          this.width = 5
      }
      getArea(){
          return this.length * this.width
      }
    }
      class Square extends Rectangle {
          constructor(){
              console.log('call child1')
              super()
              console.log('call child2')
          }
          getValue(){
              console.log('length',this.length)
              console.log('width',this.width)
          }
      }
      let square = new Square()
      square.getValue()
      /*
      call child1
      call parent
      call child2
      length 10
      width 5
      */
    複製程式碼
  • 如果派生類沒有顯式定義構造器,則系統在初始化的時候會預設呼叫super(),並使用建立例項時傳遞的所有引數
    class Square extends Rectangle {
        
    }
    // 等價於
    class Square extends Rectangle {
        constructor(..args){
            super(...args)
        }
    }
    複製程式碼

注意:如果派生類與父類初始化的引數完全相同,可以選擇省略constructor的形式

class Rectangle {
    constructor(length, width){
        this.length = length;
        this.width = width;
    }
    getArea(){
        return this.length * this.width
    }
}
class Square extends Rectangle {
    
}
let square = new Square(3,3)
console.log(square.getArea()) //9
複製程式碼

如果派生類與父類的初始化引數不一致,就得自己寫構造器constructor,手動呼叫super(),否則會得到意想不到的結果

class Rectangle {
    constructor(length, width){
        this.length = length;
        this.width = width;
    }
    getArea(){
        return this.length * this.width
    }
}
class Square extends Rectangle {
    
}
let square = new Square(3) //這裡只傳了1個引數,派生類使用預設的構造器傳遞引數,與父類構造器引數不一致,導致有些引數為undefined,呼叫計算方法是肯定為NaN
console.log(square.getArea()) //NaN
複製程式碼

靜態成員也會被繼承

class Person {
    constructor(name){
        this.name = name
    }
    static sayHi(){
       console.log('Hi') 
    }
}
class Student extends Person {

}
Person.sayHi()//Hi 
Student.sayHi()//Hi 
複製程式碼

相關文章