跳至主要内容

[JS] JavaScript 類別(Class)

function constructor 有 hoisting 的情況,所以可以寫再後面但在前面使用;但是 class constructor 沒有 hoisting 的情況 ,所有使用該 class constructor 的程式,必須放在 class constructor 後面。

ES6 Class Playground @ jsfiddle

keywords: class, constructor(data)

建立 Constructor

傳統 function constructor

過去使用 function constructor 來建構物件,並且搭配 prototype 來建議物件可使用的方法:

//  過去使用 function constructor

let Person = function (data) {
this.name = data.name;
this.age = data.age;
let profile = `${this.name}(${this.age})`; // 無法在實例中使用
};

Person.prototype.greet = function () {
console.log(`Hello ${this.name}, your age is ${this.age}`);
};

let Aaron = new Person({ name: 'Aaron', age: 28 });
let Jackson = new Person({ name: 'Jackson', age: 24 });
console.log(Aaron); // Person {name: "Aaron", age: 28}

在實例中取用不存在的變數時會得到 undefined

ES6 Class

在 ES6 中可使用關鍵字 classconstructor(self)

//  在 ES6 中可以透過 class 來建立 constructor

class Person {
constructor(data) {
this.name = data.name;
this.age = data.age;
}
}

let Aaron = new Person({ name: 'Aaron', age: 28 });
let Jackson = new Person({ name: 'Jackson', age: 24 });
console.log(Aaron); // Person {name: "Aaron", age: 28}

設定預設值:

// 同樣可以透過 object destructuring 來做到設定預設值
class Person {
constructor({ name, age = 18 } = {}) {
this.name = name;
this.age = age;
}
}

let Aaron = new Person({ name: 'Aaron' });
let Jackson = new Person({ name: 'Jackson', age: 24 });
console.log(Aaron); // Person {name: "Aaron", age: 18}
console.log(Jackson); // Person {name: "Jackson", age: 24}

class 中只能使用方法(function),不能直接代入資料。

Prototype method(原型方法)

原型方法(prototype method)指的是這些方法是建立來讓該類別的實例(instance)使用的。直接在建構式(constructor)中建立函式就是原型方法:

class Student {
constructor(firstName, lastName) {
// this 指稱的是所建立的 instance
this.firstName = firstName;
this.lastName = lastName;
}

// 原型方法
getName() {
// this 指稱的是所建立的 instance
return `${this.firstName} ${this.lastName}`;
}
}

// 類別的實力可以直接使用原型方法
aaron = new Student('Aaron', 'Chen');
console.log(aaron.getName()); // Aaron Chen

Static method(靜態方法)

keywords: static

static method 只存在 class 中,不能被 instance 所提取,只能透過指稱到該 class 才能使用該方法,可以透過 static 這個關鍵字來建立 static method:

靜態方法(static method)指的是該方法只能用在該 class 上,而不能用在 instance 上。

第一種方式:在 static 中使用 this

static 中使用 this,在使用 static method 時搭配 call 來指定 this 的對象:

// 呼叫靜態方法的第一種方式

class Bird {
constructor({ color = 'red' } = {}) {
this.color = color;
}

// 使用 static 以建立 static method,裡面用 this
static changeColor(color) {
// this 原本指稱的是所建立的 Bird 這個 Class
// 但在呼叫這個方法時,使用了 call() 把 this 改成了指定的 instance
this.color = color;
}
}

let redBird = new Bird();
console.log(redBird.color); // red
// redBird.changeColor('blue') // redBird.changeColor is not a function

Bird.changeColor.call(redBird, 'blue');
console.log(redBird.color); // blue

第二種方式:在 static 中不使用 this

static 中沒有使用 this,需要在參數中代入要指稱的對象:

// 呼叫靜態方法的第二種方式

class Bird {
constructor({ color = 'red' } = {}) {
this.color = color;
}

static changeColor(bird, color) {
// this 原本指稱的是所建立的 Bird 這個 Class
bird.color = color;
}
}

let redBird = new Bird();
console.log(redBird.color); // red
// redBird.changeColor('blue') // redBird.changeColor is not a function

Bird.changeColor(redBird, 'blue');
console.log(redBird.color); // blue

Setter 和 Getter

keywords: get, set

getter method 是在 class 中沒有帶參數就可以回傳值的方法,可以透過 get 設定;setter method 則是可以透過 = 來對物件賦值,可以使用 set 來設定:

class Person {
constructor({ firstName, lastName, country = 'Taiwan' } = {}) {
this.firstName = firstName;
this.lastName = lastName;
this.country = country;
}

// getter method
get name() {
return this.firstName + ' ' + this.lastName;
}

// setter method
set name(input) {
[this.firstName, this.lastName] = input.split(' ');
}
}

let aaron = new Person({ firstName: 'Aaron', lastName: 'Chen' });
console.log(aaron.name); // 使用 getter method,Aaron Chen
aaron.name = 'Peter Chen'; // 使用 setter method
console.log(aaron.name); // Peter Chen

使用 subclass

keywords: extends, super
  • 繼承自另一個 class 時需要使用關鍵字 extends
  • subclass 有可能覆蓋掉 superclass 的 method
  • super 的用法就像是去呼叫它的 superclass 這個 Class
class Person {
constructor({ name, age = 18 } = {}) {
this.name = name;
this.age = age;
}

dance() {
console.log('old dance');
}
}

class Student extends Person {
constructor({ name, age, interestLevel = 5 } = {}) {
// 因為這是 extends from Person,所以我們必須要在為 instance 賦值前,
// 在 Student constructor 裡面呼叫 super function,
// 呼叫 super function 的意思就是呼叫 Person 的 constructor function。
super({ name, age });
this.name = name;
this.age = age;
this.interestLevel = interestLevel;
this.grades = new Map();
}

// 如果 traditional = true,則使用 Person 的 dance();否則...
// 也就是說,subclass 是可以覆蓋 superclass 的 method
dance(traditional) {
if (traditional) {
super.dance(); // 呼叫 Person 的 dance method
} else {
const dances = ['lyrical', 'tap', 'ballet', 'jaz'];
console.log(
`${this.name} is doing the ${dances[Math.floor(Math.random() * dances.length)]}!`,
);
}
}
}

let stevenJ = new Student({ name: 'Steven', age: 22 });
stevenJ.grades.set('Math', 10);
stevenJ.dance();
console.log(stevenJ);
console.log(`stevenJ's interestLevel is: ${stevenJ.interestLevel}`);

Class fields syntax: public/private class fields

  • Class fields @ Google developer
  • Java 中物件的屬性(property)會稱作 field。

Public, Private, Static 的差別:

  • Public variable:class 內的變數可以被 instance 所存取和修改
  • Private variable:class 內的變數無法被 instance 所存取和修改
  • Static variable / static method:靜態表示該方法或變數,需要透過呼叫 Class 才能取得或使用。

在原本的 ES6 JavaScript class 語法中,並沒有辦法定義私有變數(private variable),雖然慣例上會使用 _ 作為這些變數的開頭,但實際上開發者可以直接存取和修改這些變數。例如:

class IncreasingCounter {
constructor() {
this._count = 0;
}
// ...
}

const counter = new IncreasingCounter();

// 雖然概念上希望 _count 是私有變數,但實際上可以直接對這個值進行存取和修改
counter._count;
counter._count = 42;

Public class fields

在新的 public class fields 則可以讓我們簡化對於類別的定義,也就是可以直接在 class 中定義變數,但此時 _count 仍是公開的屬性(public property)

class IncreasingCounter {
_count = 0;

get value() {
console.log('Getting the current value!');
return this._count;
}

increment() {
this._count++;
}
}

Private class fields

為了解決最初希望有 private variable 的情況,多了一種 private class fields 的語法,你只需要在變數名稱的最前方加上 # 即可:

class IncreasingCounter {
#count = 0;
get value() {
console.log('Getting the current value!');
return this.#count;
}
increment() {
this.#count++;
}
}

如此,#count 這個變數將無法在 class 內部以外的其他地方被存取:

const counter = new IncreasingCounter();
counter.#count;
// → SyntaxError
counter.#count = 42;
// → SyntaxError

Static and public/private properties

透過 class field syntax 的寫法,一樣可以用來建立公開(public)或私有(private)的靜態屬性(static property)或靜態方法(static method):

class FakeMath {
// `PI` 是一個靜態的公開屬性
// `PI` is a static public property.
static PI = 22 / 7; // Close enough.

// `#totallyRandomNumber` 是一個靜態的私有屬性
static #totallyRandomNumber = 4;

// `#computeRandomNumber` 是一個靜態的私有方法
static #computeRandomNumber() {
return FakeMath.#totallyRandomNumber;
}

// `random` 是一個靜態的公開方法 (ES2015 syntax)
// that consumes `#computeRandomNumber`.
static random() {
console.log('I heard you like random numbers…');
return FakeMath.#computeRandomNumber();
}
}

FakeMath.PI;
// → 3.142857142857143
FakeMath.random();
// logs 'I heard you like random numbers…'
// → 4
FakeMath.#totallyRandomNumber;
// → SyntaxError
FakeMath.#computeRandomNumber();
// → SyntaxError

Static Initialization Block

class MyClass {
static isStaticMethodInitialized;

static {
// 這裡的程式會在這個 Class 被 evaluate 到時被執行,因此也只會執行一次
this.isStaticMethodInitialized = true;
// ...
}
}

MyClass.isStaticMethodInitialized; // true

Static Initialization Block 就類似給 static method 用的 initialization function,它會在這個 class 的程式被 evaluate 到時就執行。

Simpler subclassing

Simpler subclassing @ Google Developers - Class fields syntax

code style

不建議使用 gettersetter,因為它可能會發生無預期的副作用,並且難以測試與維護;如果需要,使用原型方法(prototype method)自己建立,例如 getVal()setVal('Hello')

24.2 @ airbnb

參考

Public class fields / Private class fields