iT邦幫忙

2021 iThome 鐵人賽

DAY 17
1
Modern Web

舌尖上的JS系列 第 17

D17 - 吃一顆 Class 語法糖 (上)

前言

在 ES6 後,新增了 class 類別,一個更簡潔的語法來建立物件,也是建立繼承的語法糖。

必須再次強調,JavaScript 的 class 用法與其他 class-based OOP 語言中的 class 並不相同!雖然乍看寫法類似,但在 JS 中的 class 還是以原型 prototype 為基礎打造出來的,與 Java 等使用的 class 可不相同,可說是遠看像匹狼近看是隻羊,可別傻呼呼的衝進狼群內(如:Java) 咩咩咩的喊著我們都一樣唷~

(忘記 OOP 的請前往 D15 - 那個圓圓的東西 - OOP 物件導向程式設計 結束再回來)


Class 語法預計分為上下兩篇,此篇主要為基礎語法介紹
段落分為:

  • 基礎 Class 語法
  • shared method
  • extends、super
  • static
  • class 重點整理

Class 使用

class 創建物件的方法與 constructor 相同,都是 new + class 名稱 建立實例,主要差異在定義的寫法上。

在 class 定義中也分為 class declaraion 與 class expression 兩種不同寫法:

  • class delcaraion: 有命名的 class 名稱
  • class expression: 將 class 存入變數,class 可命可不命名

class 定義時會將預計加入實例中的屬性寫入關鍵字 constructor 內,當使用 new 關鍵字時,便會走訪 constructor 內的程式碼並回傳入新物件,constructor 內怎麼寫實例內的 body code 就長怎樣。
一個 class 內只會有一個 constructor

// constructor 寫法
function Cake(size, flavor) {
    this.size = size;
    this.flavor = flavor;
}

let cake1 = new Cake('M','cream')
console.log( cake1 )    // Cake {size: "M", flavor: "cream"}

// class declaration 寫法
class Cake {
  constructor(size, flavor) {
    this.size = size;
    this.flavor = flavor;
  }
}

// class expression 寫法
let Cake = class {
    constructor(size, flavor) {
        this.size = size;
        this.flavor = flavor;
    }
}

let cake2 = new Cake('M','choco');
console.log(cake2)   // Cake {size: "M", flavor: "choco"}

class 內的 shared method

使用 constructor 時,共享的 method 會另外放在 prototype 物件內,因此建立的實例 instance 都可以透過 __proto__ 在原型鏈連結到此物件; 而 class 語法將這做法簡化,shared method 不需另外寫在 prototype 物件上,可以直接寫入 class body 內。

// construcotr 的共享屬性
function Cake(flavor) {
    this.flavor = flavor;
}

Cake.prototype = {
    price: (cost) => `NTD ${cost* 1.5 }`
}

let cake1 = new Cake('cream')
cake1.price(50)   // NTD 75


// class 將共享的 method 寫在 class 內
class Cake {
    constructor(flavor) {
        this.flavor = flavor;
    }
    price(cost) {
        return `NTD$ {cost* 1.5 }`
    }
}

let cake1 = new Cake('choco')
cake1.price(50)   // NTD 75

透過 devTool 查看,果然 price method 是放在 Cake 的 prototype 物件內

extends , super 擴充類別

extends 關鍵字可以擴充繼承的類別,將 extends 後的類別加入原型鏈上!

先寫個簡單版看看怎麼使用 extends 增加類別

class Pastry {}
class Cake extends Pastry {} // 增加一個類別 Pastry 

let cake1 = new Cake()

透過 extends 可以很方便地將任何的 constructor 或 class 加入原型鏈成為 parentClass,使用 proto 查訪新加入的原型鏈成員。

Cake.prototype.__proto__ === Pastry.prototype
cake1.__proto__.__proto__ === Pastry.prototype  // true
cake1 instanceof Pastry    // true

接下來,試試除了擴增類別外,也在本身 class 內定義屬性,剛剛學過的,寫在 constructor 內,像這樣嗎?

class Cake extends Pastry {
    constructor(flavor) {
        this.flavor = flavor;
    }
    price(cost) {
        return `NTD${cost* 1.5 }`
    }
}

噢,不!
這可是會出現錯誤訊息哦

Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

錯誤訊息顯示,在使用子類別的 this 前,必須先調用母階建構函式。

先說解法就是:在子類別的 constructor 中先使用 super 呼叫母類別。

那,這是為什麼呢?
我自己的記法是 extends 指定了類別的階層,而 super 這個動作才是真正在物件上依照原型鏈順序建立屬性。

當要在子類別中使用 this 時,須先使用 super 呼叫並執行母類別的物件,先建立好母類別指定的屬性後,再接續建立子類別中的屬性,這樣也才符合為什麼 childClass 可以覆蓋 parentClass 的特性呀。

正確的程式碼應該像這樣:

class Pastry {
    constructor(name){
        this.slogan = `${name}愛吃不怕胖`
    }
    calories(piece){
        return `熱量${piece*480}k`
    }    
}

class Cake extends Pastry {
    constructor(flavor, name) {
        super(name)  // 先使用 super 呼叫並執行 Pastry
        this.flavor = flavor;
    }
    price(cost) {
        return `NTD${cost* 1.5 }`
    }
}

let cake1 = new Cake('choco', 'Hoo')
console.log( cake1 )   // Cake {slogan: "Hoo愛吃不怕胖", flavor: "choco"}
console.log( cake1.calories(3))  // 熱量1440k

static 定義靜態方法 Static Method

靜態方法 static method 存在定義的 class 中,只能由 class 存取,建立的 實例 instance 不能取到,靜態方法的語法是在前面加上 static

class Cake {
    constructor(flavor) {
        this.flavor = flavor;
    }
    price(cost) {
        return `NTD${cost* 1.5 }`
    }
   static changeFlavor(flavor){
       this.flavor = flavor;
       console.log(`flavor has changed to ${flavor}`)
   } 
}

let cake1 = new Cake('choco')
cake1.changeFlavor('mocha') // error; cake1.private() is not a function
Cake.changeFlavor('mocha')  // flavor has changed to mocha

// 可以透過 call 的方式綁定 this 為實例 cake1 進而修改
Cake.changeFlavor.call(cake1, 'mocha')  // flavor has changed to mocha
console.log(cake1.flavor)  // mocha

class 重點整理

  • 型別為 function,定義須加上關鍵字 class。
  • 寫法分為 class declaraioin & class expression,與 一般 function 類似。
  • class 命名通常以大駝峰方式。
  • class 實例內的屬性須寫在 constructor 內,class 大括號內只有一個 construtor。
  • class 的共用方法可以直接寫在 class 內,會自動納入 prototype 的屬性中供原型鏈的子階層存取。
  • 使用 extends 擴充類別,寫在 extends 前的為 childClass,extends 後的為 parentClass
  • super 用來呼叫 parentClass,加在 constructor 內的 this 程式碼之前
  • 寫在 static 開頭後的 method 只有 class 可以呼叫,稱為靜態方法

Reference

JavaScript | ES6 中最容易誤會的語法糖 Class - 基本用法
MDN - class
Class basic syntax
[JS] JavaScript 類別(Class)
JavaScript Class

結語

基本的 Class 語法介紹完畢!
明天內容為 constructor 與 class 的差異比較、真正的 class 與 prototyped-based 的 class 差異。


上一篇
D16 - 那個圓圓的東西 - 物件原型 & 原型鏈
下一篇
D18 - 吃一顆 Class 語法糖 (下)比較 Constructor 與 Class
系列文
舌尖上的JS30

尚未有邦友留言

立即登入留言