[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 中可使用關鍵字 class
和 constructor(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
不建議使用 getter
和 setter
,因為它可能會發生無預期的副作用,並且難以測試與維護;如果需要,使用原型方法(prototype method)自己建立,例如 getVal()
和 setVal('Hello')
。
24.2 @ airbnb
參考
- Classes @ MDN > Web technology for developers
- ES6 In Depth: Classes @ Mozilla Hacks
- ES6 In Depth: Subclassing @ Mozilla Hacks
- Classes @ Exploring ES6
Public class fields / Private class fields
- Public and private class fields @ Google Developers
- ES proposal: class fields @ 2ality