JavaScript
Class
Private Fields (#)
เรียนรู้การใช้ # สร้าง private field — ซ่อนข้อมูลภายใน class ไม่ให้ใครอ่านหรือแก้จากภายนอก — ตั้งแต่ประกาศ #field, pattern ซ่อนข้อมูล, validation, static #privateField, ไปจนถึงกฎสำคัญและข้อผิดพลาดที่พบบ่อย ผ่าน Lab 4 ข้อ
# (Private Fields) คืออะไร — field ที่เข้าถึงได้เฉพาะภายใน class
**Private field** คือ field ที่ใช้ `#` นำหน้าชื่อ — ทำให้ JavaScript จำกัดการเข้าถึงให้**ภายใน class เท่านั้น** โค้ดภายนอก — แม้กระทั่ง instance ที่สร้างจาก `new` — ก็ไม่สามารถอ่านหรือเขียน private field ได้โดยตรง **ปัญหา**: field ปกติ (`this.name`) ใครก็เข้าถึงได้ — `obj.name = "ค่าใหม่"` เปลี่ยนจากข้างนอกได้เสมอ **วิธีแก้**: ใช้ `#` prefix — `this.#name` — ล็อก field ไว้ภายใน class เปรียบง่าย ๆ: field ปกติเหมือนสมบัติที่วางกลางบ้านใครก็หยิบได้ — private field เหมือนสมบัติในตู้เซฟที่มีแค่เจ้าของเปิดได้
`#count` ถูกประกาศที่ class level — เข้าถึงได้เฉพาะภายใน `increment()` และ `getCount()` — อ่านหรือเขียนจากภายนอกไม่ได้
class Counter {
#count = 0; // private field — ต้องประกาศที่ระดับ class
increment() {
this.#count = this.#count + 1; // ✅ เข้าถึง #count ภายใน class
}
getCount() {
return this.#count; // ✅ อ่าน #count ผ่าน method
}
}
const c = new Counter();
c.increment();
c.increment();
console.log(c.getCount()); // 2 — ✅ อ่านผ่าน method ที่ class จัดให้
// ❌ อ่าน/เขียนจากภายนอกไม่ได้
// console.log(c.#count); // SyntaxError
// c.#count = 999; // SyntaxErrorจากตัวอย่าง: - `#count = 0` — ประกาศ private field ที่ class level — **ห้ามประกาศใน constructor** - `this.#count` — ใช้ private field **ทุกที่ใน class** — constructor, method, getter, setter ใช้ `#` เหมือนกันหมด - `c.#count` — เข้าถึงจากภายนอกไม่ได้ — JavaScript จะ throw `SyntaxError` ทันที - `c.getCount()` — อ่านข้อมูลผ่าน public method — นี่คือ pattern มาตรฐานของการใช้ private field
กฎการประกาศ — ต้องประกาศ #field ที่ระดับ class เท่านั้น
Private field มีกฎสำคัญเรื่องตำแหน่งการประกาศ: - **ต้องประกาศที่ class level** — ก่อน constructor หรือ method ใด ๆ - **ห้ามประกาศใน constructor** — `this.#field = value` ใน constructor ใช้ได้เฉพาะกับ field ที่ประกาศไว้แล้ว - **ใช้ `this.#fieldName` เหมือนกันทุกที่** — constructor, method, getter, setter
`#brand` และ `#model` ประกาศที่ class level — constructor ใช้ `this.#field` กำหนดค่า — method ใช้ `this.#field` อ่านค่า
class Car {
#brand; // ✅ ประกาศที่ class level
#model; // ✅ ประกาศที่ class level
constructor(brand, model) {
this.#brand = brand; // ✅ กำหนดค่า private field
this.#model = model; // ✅ กำหนดค่า private field
}
getDescription() {
return this.#brand + " " + this.#model; // ✅ อ่าน private field
}
}
const car = new Car("Toyota", "Camry");
console.log(car.getDescription()); // "Toyota Camry"`this.#title = title` ใน constructor โดยไม่ประกาศ `#title` ที่ class level — JavaScript จะ throw `SyntaxError`
class Book {
constructor(title) {
this.#title = title; // ❌ #title ไม่เคยถูกประกาศที่ class level
}
}
// SyntaxError: Private field '#title' must be declared
// in an enclosing class**สรุปกฎ**: คิดว่า `#field` เป็นการจดทะเบียน field ไว้ที่ class — JavaScript ต้องเห็น `#field` ที่ class level ก่อน ถึงจะยอมให้ใช้ `this.#field` ในโค้ดทุกที่ของ class **วิธีจำ**: ประกาศ `#field` ก่อน — เหมือนจองตัวแปรให้ JavaScript รู้จัก — แล้วค่อยใช้ `this.#field` ได้ทุกที่
Private field + public method — pattern ซ่อนข้อมูล เปิดทางเข้าผ่าน method
Private field มีประโยชน์มากที่สุดเมื่อใช้คู่กับ public method — class เป็นคนควบคุมว่าข้อมูลจะถูกอ่านหรือแก้ไขอย่างไร **ตัวอย่างจริง**: BankAccount — ซ่อน `#balance` ไว้ → ให้ `deposit()` ถอนเงิน `withdraw()` เติมเงิน — และอนุญาตให้อ่านยอดผ่าน `getBalance()` เท่านั้น โค้ดภายนอกเปลี่ยนยอดตรง ๆ ไม่ได้ — ต้องผ่าน method ที่ class กำหนด — ทำให้ class ใส่ validation หรือ logic ก่อนแก้ค่า private field ได้
`#balance` ซ่อนไว้ — แก้ค่าได้ผ่าน `deposit()` และ `withdraw()` เท่านั้น — `withdraw()` ตรวจสอบว่ายอดไม่ติดลบ
class BankAccount {
#balance = 0;
deposit(amount) {
this.#balance = this.#balance + amount;
console.log("ฝาก " + amount + " — ยอดคงเหลือ: " + this.#balance);
}
withdraw(amount) {
if (amount > this.#balance) {
console.log("ยอดเงินไม่พอ — มีแค่ " + this.#balance);
return;
}
this.#balance = this.#balance - amount;
console.log("ถอน " + amount + " — ยอดคงเหลือ: " + this.#balance);
}
getBalance() {
return this.#balance;
}
}
const account = new BankAccount();
account.deposit(1000); // "ฝาก 1000 — ยอดคงเหลือ: 1000"
account.withdraw(300); // "ถอน 300 — ยอดคงเหลือ: 700"
account.withdraw(1000); // "ยอดเงินไม่พอ — มีแค่ 700"
console.log(account.getBalance()); // 700 — ✅ อ่านผ่าน method
// console.log(account.#balance); // ❌ SyntaxErrorสังเกต pattern: 1. `#balance` — private — กันการเข้าถึงโดยตรง 2. `deposit(amount)` — public — ควบคุมวิธีเพิ่มเงิน 3. `withdraw(amount)` — public — ตรวจสอบก่อนลดเงิน (กันติดลบ) 4. `getBalance()` — public — เปิดให้อ่านข้อมูลอย่างเดียวโดยไม่ให้แก้ **ประโยชน์**: ภายนอกเรียก `account.#balance = -999` ไม่ได้ — ป้องกันข้อมูลเสียหายโดยไม่ตั้งใจ — และ `withdraw()` ใส่ validation กันถอนเกินยอด
Private fields + Inheritance — ลูกคลาสไม่สามารถเข้าถึง #field ของพ่อโดยตรง
Private field **ไม่ถูกสืบทอด** แบบที่ public field เป็น — child class ไม่สามารถเข้าถึง `#field` ของ parent ได้โดยตรง **เหตุผล**: `#` หมายถึง private จริง ๆ — แม้แต่คลาสลูกก็ไม่มีสิทธิ์เข้าถึง — ถ้าอยากให้ child ใช้ข้อมูลของ parent ต้องให้ parent ประกาศ public method ไว้เป็นสะพานเชื่อม
`Child` extends `Parent` แต่ `#secret` เป็น private — `Child` เข้าถึง `this.#secret` โดยตรงไม่ได้
class Parent {
#secret = "password123";
getSecret() {
return this.#secret; // ✅ Parent เข้าถึง #secret ของตัวเองได้
}
}
class Child extends Parent {
reveal() {
// ❌ Child เข้าถึง #secret ของ Parent ไม่ได้
// return this.#secret; // SyntaxError
}
}
const child = new Child();
// console.log(child.#secret); // ❌ SyntaxError
console.log(child.getSecret()); // ✅ "password123" — ผ่าน method ของ parent- **Child เข้าถึง `#field` ของ parent ไม่ได้** — `this.#field` ใน child class ที่ `#field` ประกาศใน parent จะ throw `SyntaxError`
- **ทางออก**: Parent ต้องมี public method ที่อ่านหรือเขียน `#field` — child ใช้ method นั้นแทนการเข้าถึง `#field` โดยตรง
- **Child ประกาศ `#field` ชื่อเดียวกับ parent ได้** — `#field` ใน parent กับ `#field` ใน child เป็นคนละ field กัน — ไม่ใช่ override — ต่างคนต่างมีของตัวเอง
- **`#privateMethod()` ก็ใช้กฎเดียวกับ `#field`** — child เข้าถึง private method ของ parent ไม่ได้ — ต้องให้ parent เปิด public method ไว้
`static #privateField` — private field ระดับ class
`static` กับ `#` ใช้ร่วมกันได้ — `static #field` คือ private field ที่เป็นของ class — ไม่ใช่ของ instance **ประโยชน์**: ซ่อน configuration, secret key, หรือข้อมูลกลางที่ class ใช้ภายใน — ไม่ให้ใครอ่านหรือแก้จากภายนอก **ความแตกต่างกับ `static` ปกติ**: `static DEFAULT = 10` เปลี่ยนจากภายนอกได้ — `static #DEFAULT = 10` เปลี่ยนจากภายนอกไม่ได้
`static #apiKey` — ใช้ภายใน class เท่านั้น — เข้าถึงผ่าน static method `getApiKey()` — ภายนอกเรียก `ApiService.#apiKey` ไม่ได้
class ApiService {
static #apiKey = "sk-12345-abcde"; // static private field
static getApiKey() {
return this.#apiKey; // ✅ static method เข้าถึง static #field ได้
}
static updateKey(newKey) {
this.#apiKey = newKey; // ✅ แก้ static #field ผ่าน static method
}
}
console.log(ApiService.getApiKey()); // "sk-12345-abcde"
ApiService.updateKey("sk-99999-zzzzz");
console.log(ApiService.getApiKey()); // "sk-99999-zzzzz"
// ❌ อ่าน static #field จากภายนอกไม่ได้
// console.log(ApiService.#apiKey); // SyntaxError`static MAX` — อ่านจากภายนอกได้ — `static #SECRET` — อ่านจากภายนอกไม่ได้
class Config {
static MAX_RETRY = 3; // public static — อ่านจากภายนอกได้
static #SECRET_KEY = "xyz"; // private static — อ่านจากภายนอกไม่ได้
static getSecret() {
return this.#SECRET_KEY;
}
}
console.log(Config.MAX_RETRY); // 3 — ✅ public static
// console.log(Config.#SECRET_KEY); // ❌ SyntaxError
console.log(Config.getSecret()); // "xyz" — ✅ ผ่าน static method**กฎของ `static #field`**: - ใช้ `static #fieldName` ประกาศที่ class level - เข้าถึงด้วย `this.#fieldName` ใน static method (เพราะ `this` = class) - instance method เข้าถึง `static #field` ผ่าน `ClassName.method()` หรือ `this.constructor.method()` — แต่เข้าถึงโดยตรงด้วย `this.#field` ไม่ได้ (เพราะเป็นคนละ context) - เหมือน `static` ปกติแต่เพิ่มความ private — ใช้เมื่ออยากได้ข้อมูลกลางที่ class เท่านั้นใช้ได้
public field + #private field ใช้ร่วมกัน — getter/setter pattern
Private field ใช้เดี่ยว ๆ อย่างเดียวก็ได้ — แต่ pattern ที่มีประสิทธิภาพที่สุดคือการใช้ public field หรือ getter/setter เป็นตัวกลาง เปิดช่องให้เข้าถึงหรือแก้ไข private field อย่างควบคุม **Pattern ทั่วไป**: - `#price` — เก็บค่าจริง (private) - `get price()` — public getter — อ่าน `#price` ผ่าน syntax สวย ๆ `obj.price` - `set price(value)` — public setter — ใส่ validation ก่อนเขียน `#price`
`get price()` — อ่าน `#price` ด้วย `obj.price` — `set price()` — ตรวจสอบก่อนอนุญาตให้เปลี่ยน `#price`
class Product {
#price = 0; // private — เก็บค่าจริง
constructor(name, price) {
this.name = name; // public field — อ่าน/เขียนจากภายนอกได้
this.price = price; // เรียก setter — ผ่าน validation
}
get price() {
return this.#price; // ✅ getter — อ่าน #price
}
set price(value) {
if (value < 0) {
console.log("ราคาต้องไม่ต่ำกว่า 0");
return;
}
this.#price = value; // ✅ setter — เขียน #price หลังตรวจสอบ
}
}
const p = new Product("เมาส์", 590);
console.log(p.price); // 590 — ✅ อ่านผ่าน getter
p.price = 999; // ✅ เขียนผ่าน setter — ผ่าน validation
console.log(p.price); // 999
p.price = -100; // "ราคาต้องไม่ต่ำกว่า 0" — setter ป้องกัน
console.log(p.price); // 999 — ค่าเดิมไม่เปลี่ยน
// ❌ p.#price — SyntaxError — #price ไม่สามารถเข้าถึงจากภายนอก**Pattern นี้ให้อะไร**: - ภายนอกใช้ `obj.price` เหมือน field ปกติ — ใช้งานสะดวก - ภายในเก็บค่าใน `#price` — ป้องกันการแก้ไขโดยตรงแบบ `obj.#price = -1` - `set price()` ใส่ validation ได้ — ถ้าค่าไม่ผ่าน กันไม่ให้เปลี่ยน Pattern เดียวกันนี้ใช้ได้กับ `static #field` + `static get/set` เช่นกัน
กฎสำคัญของ private fields
| กฎ | คำอธิบาย | ตัวอย่าง |
|---|---|---|
| ใช้ `#` นำหน้าชื่อ field | private field ต้องขึ้นต้นด้วย `#` — เช่น `#count`, `#balance`, `#name` — `#` เป็นส่วนหนึ่งของชื่อ | `#count` ✅ / `count` (public) / `_count` (convention ไม่ใช่ private จริง) |
| ประกาศที่ class level เท่านั้น | ต้องมี `#field = value;` หรือ `#field;` ที่ class level — ห้ามประกาศใน constructor | `class X { #y; constructor() { this.#y = 1; } }` ✅ / `class X { constructor() { this.#y = 1; } }` ❌ |
| เข้าถึง `#field` ภายใน class ด้วย `this.#field` | ใน constructor, method, getter, setter — ใช้ `this.#fieldName` เหมือนกันทุกที่ | `this.#count` — ใช้ได้ทุกที่ใน class |
| เข้าถึง `#field` จากภายนอกไม่ได้ | `obj.#field` — throw `SyntaxError` — แม้เป็น instance ก็เข้าไม่ถึง — ต้องใช้ method ที่ class จัดให้ | `obj.#count` ❌ / `obj.getCount()` ✅ |
| `delete` ใช้กับ `#field` ไม่ได้ | JavaScript ไม่อนุญาตให้ลบ private field — `delete this.#field` จะ throw `SyntaxError` | `delete this.#count` ❌ |
| Child class เข้าถึง `#field` ของ parent ไม่ได้ | Private field ไม่ถูกสืบทอด — child ใช้ `this.#fieldOfParent` ไม่ได้ — ต้องผ่าน public method ของ parent | child ใช้ `this.#parentField` ❌ / child ใช้ `this.getParentField()` ✅ |
| `this.#x` กับ `this.x` เป็นคนละ field | `#` เป็นส่วนหนึ่งของชื่อ — `this.#x` และ `this.x` เป็นคนละ field ไม่เกี่ยวข้องกัน — มีค่าแยกกัน | `this.#x = 1; this.x = 2; // #x = 1, x = 2` |
| `static #field` ใช้กฎ private + static รวมกัน | ประกาศด้วย `static #field` — เข้าถึงด้วย `this.#field` ใน static method — instance method เข้าถึงโดยตรงไม่ได้ | `static #key; static getKey() { return this.#key; }` |
ข้อผิดพลาดที่พบบ่อย
- **เข้าถึง `#field` จากภายนอก class**: `obj.#field` — JavaScript จะ throw `SyntaxError` — private field เข้าถึงจากที่ไหนก็ไม่ได้นอก class — ต้องใช้ method ที่ class เปิดไว้
- **ประกาศ `#field` ใน constructor**: `constructor() { this.#field = value; }` — โดยไม่มี `#field` ที่ class level — JavaScript จะ throw `SyntaxError: Private field '#field' must be declared in an enclosing class` — ต้องประกาศที่ class level ก่อน
- **เข้าใจผิดว่า `this.x` ใช้กับ private field ได้**: private field มี `#` เป็นส่วนหนึ่งของชื่อ — `this.#x` เท่านั้นที่อ่าน private field — `this.x` เป็นคนละ field — ถ้าใช้ `this.x` จะอ่าน public field หรือสร้าง property ใหม่แทน
- **พยายาม override `#field` ใน child class**: `#field` ไม่ถูก override — `#field` ของ parent กับ `#field` ของ child เป็นคนละ field — child ประกาศ `#field` ได้แต่จะไม่เชื่อมกับของ parent
- **ลืมใส่ `#` ตอนใช้ field**: `#field = 0` ตอนประกาศ แต่ใช้ `this.field` ตอนอ่าน — JavaScript จะอ่าน public field ที่ไม่มีอยู่จริง — ได้ `undefined` — ต้องใช้ `this.#field` เสมอ
- **พยายามใช้ `delete` กับ private field**: `delete this.#field` — JavaScript ไม่อนุญาต — throw `SyntaxError` — private field ถูกจัดการโดย class — ลบไม่ได้ — ถ้าอยากรีเซ็ต ให้กำหนดค่าใหม่ เช่น `this.#field = 0`
- **คิดว่า `_fieldName` (underscore) คือ private จริง**: `_fieldName` เป็นแค่ naming convention — ไม่ใช่ private จริง — ถูกอ่าน/เขียนจากภายนอกได้ปกติ — `#fieldName` เท่านั้นที่เป็น private จริงใน JavaScript
- **ใช้ `#field` ใน child class โดยอ้างอิง parent**: child class มี `#field` ชื่อเดียวกับ parent — `this.#field` ใน child จะหมายถึง `#field` ของ child — ไม่ใช่ของ parent — ทั้งสองเป็นอิสระต่อกัน — ถ้าอยากใช้ของ parent ต้องให้ parent มี public method