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 差异。


<<:  【day17】 时间格式 X 搜寻结果排序

>>:  第17车厢-超实用!tab页签切换:data-*应用篇

Ubuntu巡航记(4) -- Rust 安装

前言 Rust 是一个现代版的 C/C++ 程序语言,它加入物件导向、套件安装(cargo)、函数式...

Day29-终於要进去新手村了-HTML DOM 范例

一样要说明这是由彭彭影片撷取出来的例题 <!DOCTYPE html> <html...

反思与第二部序章

魔鬼藏在细节 在上一篇中的最後我问了一个问题:“为什麽没有使用 Flowable 而是继续用 Obs...

Day13 Random

Random随机变数 Java里面本身有个语法就是可以帮助我们产生随机变数,这个语法是Math.ra...

#5-中秋月亮晕起来!不规则Blob球球(CSS)

这一两年很常在网页设计中看到这种不规则的小东西出现,甚至还会像波浪一样动。 之前傻傻地用Svg做,然...