之前的StudySession可以透過方法增加分鐘,但外部仍然能直接寫session.minutes = -100。這會繞過原本希望使用的方法,也可能讓物件保存不合理的資料。
物件導向中的封裝,是把資料和操作資料的方法放在一起,並限制外部只能透過允許的方式修改內容。JavaScript類別可以使用#開頭的私有欄位,下面將私有資料、修改方法和讀取方式都放進同一個類別:
class StudySession {
#minutes = 0;
constructor(topic, goalMinutes = 60) {
this.topic = topic;
this.goalMinutes = goalMinutes;
}
addMinutes(minutes) {
if (typeof minutes !== "number" || minutes <= 0) {
throw new RangeError("分鐘必須是大於 0 的數字");
}
this.#minutes += minutes;
}
get minutes() {
return this.#minutes;
}
get isComplete() {
return this.#minutes >= this.goalMinutes;
}
}
#minutes屬於每一個StudySession實例,但只能在這個類別內使用。在類別外寫session.#minutes會直接出現語法錯誤,因此外部不能跳過類別提供的方法任意修改。
addMinutes()讓所有分鐘變更都先經過同一個檢查,只要外部都透過這個方法操作,就能避免負數或錯誤型別被加入。
私有欄位不能直接從外部讀取。如果希望外部可以查看結果,但不能直接修改,可以提供minutes和isComplete兩個 getter。
getter使用時看起來像屬性,不需要加小括號:
console.log(session.minutes);
console.log(session.isComplete);

這裡和作用域篇章看起來一樣都有「外部不能取得」的效果,但原因不同。作用域是依照變數宣告的位置,決定一個變數名稱能在哪個函式或區塊中使用;私有欄位則是類別提供的存取限制,每個實例都有自己的#minutes,只有這個類別的方法能操作它。
簡單來說,作用域處理的是變數在哪裡可見,封裝處理的是物件允許外部怎麼使用資料。兩者都能避免資料被隨意操作,但用途與規則並不相同。