โหมดมืด
บทที่ 08 — Class และ Modifier
⏱ ใช้เวลา 2–3 ชั่วโมง
บทนี้เรียนอะไร
class มีอยู่ใน JavaScript อยู่แล้ว — TypeScript เพิ่ม "ระบบ type" และ "ตัวกำกับการเข้าถึง" (modifier) เข้าไป ทำให้ class ปลอดภัยและสื่อความหมายมากขึ้น
บทนี้จะสอน:
- การใส่ type ให้ property และ method ของ class
- Parameter property — ไวยากรณ์ย่อที่ TypeScript เพิ่มมา
- Access modifier —
public,private,protected,readonly— และแนวคิด encapsulation (การห่อหุ้มข้อมูล) - การสืบทอด (inheritance), abstract class, การ implement interface
- การใช้ class กับ generic
- เรื่องที่มืออาชีพถกเถียงกัน — "ควรใช้ class หรือไม่"
💡 บทนี้ถ้าคุณแม่นเรื่อง
classใน JavaScript อยู่แล้ว จะอ่านง่ายมาก — เพราะ TypeScript แค่เพิ่ม type ทับลงไป ถ้ายังไม่แม่น แนะนำทบทวนบท class ใน Javascript ควบคู่ไปด้วย
Part 1: Class พื้นฐานพร้อม type
1.1 หน้าตาของ class ใน TypeScript
typescript
class User {
name: string; // ★ ประกาศ property พร้อม type (TypeScript บังคับ)
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string {
return `สวัสดี ฉันชื่อ ${this.name}`;
}
}
const alice = new User("Alice", 30);
console.log(alice.greet()); // "สวัสดี ฉันชื่อ Alice"ส่วนที่ TypeScript เพิ่มจาก JavaScript:
- บรรทัด
name: string;และage: number;— ต้อง "ประกาศ property พร้อม type" ไว้บนสุดของ class (JavaScript ไม่บังคับ แต่ TypeScript บังคับ) - type ของพารามิเตอร์ใน
constructor - return type ของ method (
greet(): string)
1.2 ทำไมต้องประกาศ property ไว้บนสุด
ใน JavaScript คุณ assign this.name ได้เลยโดยไม่ต้องประกาศ แต่ TypeScript ต้องรู้ "รูปร่างของ object" ล่วงหน้า — มันจึงต้องการให้เราประกาศ property ทั้งหมดไว้ก่อน เพื่อจะตรวจสอบได้ว่าเราเข้าถึง property ถูกต้อง
Part 2: Parameter Property — ไวยากรณ์ย่อสุดสะดวก
2.1 ปัญหา — โค้ดซ้ำซาก
สังเกตโค้ดใน Part 1 — เราเขียนชื่อ property ถึง 3 ครั้ง สำหรับแต่ละตัว:
typescript
class User {
name: string; // 1. ประกาศ
constructor(name: string) { // 2. รับเป็นพารามิเตอร์
this.name = name; // 3. assign
}
}ถ้ามี property 10 ตัว โค้ดส่วนนี้จะยาวและน่าเบื่อมาก
2.2 ทางออก — Parameter Property
TypeScript มีไวยากรณ์ย่อพิเศษ — แค่ใส่ access modifier (เช่น public) หน้าพารามิเตอร์ใน constructor มันจะ ประกาศ + รับ + assign ให้อัตโนมัติ:
typescript
class User {
constructor(
public name: string,
public age: number,
) {}
// ไม่ต้องเขียน this.name = name; เลย — TypeScript ทำให้
greet(): string {
return `สวัสดี ${this.name}`;
}
}
const alice = new User("Alice", 30);
console.log(alice.name); // "Alice" — ใช้ได้ปกติโค้ดสั้นลงมาก! public name: string ในตำแหน่งพารามิเตอร์ constructor = "สร้าง property name ที่เป็น public และ assign ค่าให้อัตโนมัติ"
📌 Parameter property เป็นฟีเจอร์เฉพาะของ TypeScript (JavaScript ไม่มี) — ต้องใส่ modifier (
public/private/protected/readonly) นำหน้าถึงจะทำงาน ถ้าไม่ใส่จะเป็นแค่พารามิเตอร์ธรรมดา
Part 3: Access Modifier — ควบคุมการเข้าถึง
3.1 แนวคิด — "Encapsulation" (การห่อหุ้ม)
ก่อนเรียนไวยากรณ์ ต้องเข้าใจ "ทำไม" ก่อน
ลองนึกถึงรีโมททีวี — มันมีปุ่มให้คุณกด (เปิด/ปิด, เปลี่ยนช่อง) แต่วงจรอิเล็กทรอนิกส์ข้างในถูก "ห่อ" ไว้ คุณแตะมันไม่ได้ ทำไม? เพราะถ้าใครก็มาแตะวงจรข้างในได้ มันจะพังง่าย
class ก็เหมือนกัน — บาง property/method ควรเป็น "ปุ่มสาธารณะ" ที่ใครก็ใช้ได้ บางอย่างควรเป็น "วงจรภายใน" ที่ซ่อนไว้ การควบคุมแบบนี้เรียกว่า encapsulation และ access modifier คือเครื่องมือทำมัน
3.2 modifier ทั้ง 4 ตัว
typescript
class BankAccount {
public owner: string; // เข้าถึงได้จากทุกที่
private balance: number; // เข้าถึงได้เฉพาะภายใน class นี้
protected accountType: string; // เข้าถึงได้ใน class นี้ + class ลูก
readonly accountNumber: string;// อ่านได้ทุกที่ แต่แก้ไม่ได้
constructor(owner: string, accountNumber: string) {
this.owner = owner;
this.balance = 0;
this.accountType = "savings";
this.accountNumber = accountNumber;
}
}| modifier | เข้าถึงจากไหนได้บ้าง |
|---|---|
public | ทุกที่ (เป็นค่าเริ่มต้น ถ้าไม่ใส่อะไร) |
private | เฉพาะภายใน class เดียวกันเท่านั้น |
protected | ภายใน class นี้ และ class ที่สืบทอดไป |
readonly | อ่านได้ทุกที่ แต่กำหนดค่าได้ครั้งเดียว (ตอนสร้าง) |
3.3 ตัวอย่างการใช้ private ปกป้องข้อมูล
typescript
class BankAccount {
private balance: number = 0;
deposit(amount: number): void {
if (amount <= 0) throw new Error("จำนวนเงินต้องมากกว่า 0");
this.balance += amount;
}
getBalance(): number {
return this.balance;
}
}
const account = new BankAccount();
account.deposit(1000);
console.log(account.getBalance()); // 1000 ✅
account.balance = 999999; // ❌ Error! balance เป็น privateเห็นประโยชน์ไหม — เราบังคับให้ทุกคน "ฝากเงินผ่าน deposit()" เท่านั้น ซึ่งมีการตรวจสอบ (amount > 0) ไม่มีใครมาแอบแก้ balance ตรง ๆ ได้ ข้อมูลจึงปลอดภัยและน่าเชื่อถือ
3.4 ⚠️ จุดสำคัญ — private ของ TypeScript ไม่ใช่ความลับจริง
นี่คือเรื่องที่มือใหม่ต้องรู้ — private ของ TypeScript มีผลแค่ตอน compile-time เท่านั้น
ทบทวนคำสองคำนี้ก่อน: compile-time = ตอนคอมไพล์ (ช่วงที่ TypeScript แปลงโค้ดเป็น JavaScript และตรวจ type) ส่วน runtime = ตอนรันโปรแกรมจริง (ช่วงที่โค้ดทำงานจริง) — จำเรื่อง type erasure จากบทที่ 00 ได้ไหม type ทั้งหลายจะถูกลบทิ้งก่อนถึง runtime
typescript
const account = new BankAccount();
account.balance = 999; // ❌ TypeScript ฟ้อง
(account as any).balance = 999; // 😱 แต่ทำแบบนี้ได้! ตอน runtime ไม่มีการป้องกันprivate ของ TypeScript เป็นเหมือน "ป้ายห้ามเข้า" — มันเตือนเราตอนเขียนโค้ด แต่ตอน runtime จริง ๆ ค่ายังเข้าถึงได้
3.5 ความลับจริง ๆ — ใช้ # (private ของ JavaScript)
ถ้าต้องการ "ความลับจริง" ที่ป้องกันได้ทั้งตอน compile และ runtime ให้ใช้ # ซึ่งเป็นฟีเจอร์ของ JavaScript เอง:
typescript
class BankAccount {
#balance: number = 0; // ★ ใช้ # นำหน้า = private จริงระดับ runtime
deposit(amount: number): void {
this.#balance += amount;
}
getBalance(): number {
return this.#balance;
}
}
const account = new BankAccount();
// ❌ บรรทัดข้างล่างนี้ "compile ไม่ผ่าน" — เป็น SyntaxError ตั้งแต่ตอน parse:
// (account as any).#balance;
//
// เหตุผล: `#balance` เป็น private field จริงของ JavaScript — ไวยากรณ์ `#ชื่อ`
// อ้างถึงได้เฉพาะ "ภายใน class body ที่ประกาศมัน" เท่านั้น
// การ cast เป็น any ไม่ช่วยอะไรเลย เพราะ `#` ถูกบล็อกที่ระดับไวยากรณ์ (syntactic) ไม่ใช่ระดับ type
//
// ถ้าจะ "พยายาม" เข้าถึง runtime จริง ๆ ก็ทำไม่ได้เช่นกัน:
console.log((account as any).balance); // undefined — property ชื่อ "balance" (ไม่มี #) ไม่มีอยู่
console.log(Object.keys(account)); // [] — # field ไม่โผล่ใน Object.keys🎯 คำแนะนำปี 2026: สำหรับ property ที่ต้องการความเป็นส่วนตัวจริง ๆ ใช้
#แทนprivateเพราะมันป้องกันได้จริงทั้งสองช่วงเวลา ส่วนprivate/protected/publicของ TypeScript ยังมีประโยชน์ในแง่ "สื่อเจตนา" และใช้กับ parameter property ได้
Part 4: Static Member — สมาชิกของ "class" ไม่ใช่ของ "instance"
ปกติ property/method เป็นของ "แต่ละ instance" (แต่ละ object ที่ new ออกมา) แต่ static member เป็นของ "ตัว class เอง":
typescript
class MathUtils {
static readonly PI = 3.14159;
static square(n: number): number {
return n * n;
}
}
// เรียกผ่านชื่อ class ตรง ๆ — ไม่ต้อง new
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.square(5)); // 25ใช้ static กับสิ่งที่ "ไม่ผูกกับ instance ใด instance หนึ่ง" เช่น ค่าคงที่ หรือฟังก์ชันช่วยเหลือ
Part 5: Getter และ Setter — property ที่มีลอจิกซ่อน
get และ set ให้เรา "ทำให้ method ใช้งานเหมือน property":
typescript
class Temperature {
#celsius: number;
constructor(celsius: number) {
this.#celsius = celsius;
}
// getter — เรียกใช้เหมือนอ่าน property
get celsius(): number {
return this.#celsius;
}
// setter — เรียกใช้เหมือนเขียน property แต่มีการตรวจสอบ
set celsius(value: number) {
if (value < -273.15) {
throw new Error("ต่ำกว่าศูนย์สัมบูรณ์ไม่ได้");
}
this.#celsius = value;
}
// getter ที่ "คำนวณ" ค่าใหม่ทุกครั้ง
get fahrenheit(): number {
return this.#celsius * 9 / 5 + 32;
}
}
const temp = new Temperature(25);
console.log(temp.celsius); // 25 ← เรียก getter (ดูเหมือนอ่าน property)
temp.celsius = 30; // ← เรียก setter (ดูเหมือนเขียน property)
console.log(temp.fahrenheit); // 86 ← getter ที่คำนวณ
temp.celsius = -300; // ❌ Error! setter ตรวจสอบแล้วโยน errorข้อดี: คนใช้เขียน temp.celsius = 30 เหมือนเขียน property ธรรมดา แต่จริง ๆ มีการตรวจสอบซ่อนอยู่
Part 6: Inheritance — การสืบทอด
6.1 extends และ super
class สืบทอดจาก class อื่นได้ด้วย extends — class ลูกจะได้ทุกอย่างของ class แม่:
typescript
class Animal {
constructor(public name: string) {}
eat(): string {
return `${this.name} กำลังกิน`;
}
}
class Dog extends Animal {
constructor(name: string, public breed: string) {
super(name); // ★ เรียก constructor ของ class แม่ — ต้องเรียกก่อนใช้ this
}
bark(): string {
return `${this.name} เห่า`; // ใช้ name ที่สืบทอดมาจาก Animal ได้
}
}
const rex = new Dog("เรกซ์", "ลาบราดอร์");
console.log(rex.eat()); // "เรกซ์ กำลังกิน" ← method จาก Animal
console.log(rex.bark()); // "เรกซ์ เห่า" ← method ของ Dog เอง⚠️ กฎสำคัญ: ใน constructor ของ class ลูก ต้องเรียก super(...) ก่อน ใช้ this เสมอ
6.2 override — เขียน method ทับของ class แม่
class ลูก "เขียนทับ" method ของ class แม่ได้ ควรใส่คำว่า override กำกับให้ชัด (คำกำกับ override ต้องใช้ TypeScript 4.3 ขึ้นไป — ใน TS 5.7 ที่เราใช้ในเล่มนี้รองรับเต็มที่):
typescript
class Dog extends Animal {
constructor(name: string) {
super(name);
}
override eat(): string {
// เรียก method เดิมของ class แม่ด้วย super
return `${super.eat()} อย่างเอร็ดอร่อย`;
}
}ทำไมต้องใส่ override? — ในบทที่ 01 เราเปิด noImplicitOverride: true ไว้ (ตัวเลือก tsconfig ที่ "บังคับใช้คำกำกับ override" เมื่อเขียนทับ method) ประโยชน์: ถ้าวันหนึ่งมีคนเปลี่ยนชื่อ method ใน class แม่ (เช่นจาก eat เป็น consume) — method eat ใน class ลูกที่มี override กำกับจะ error ทันที (เพราะไม่มี eat ใน class แม่ให้ override แล้ว) ช่วยให้ refactor ปลอดภัย
Part 7: class แม่แบบ (Abstract Class) — class ที่ "ยังไม่สมบูรณ์"
class แม่แบบ (Abstract Class) คือ class ที่ออกแบบมาให้ "เป็นแม่แบบ" — มันสร้าง instance ตรง ๆ ไม่ได้ ต้องให้ class ลูกมาสืบทอดและเติมส่วนที่ขาด
typescript
abstract class Shape {
// abstract method — ประกาศแต่ไม่มี body — class ลูก "ต้อง" มา implement
abstract getArea(): number;
// method ปกติ — class ลูกใช้ได้เลย
describe(): string {
return `รูปทรงนี้มีพื้นที่ ${this.getArea()} ตารางหน่วย`;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
// ต้อง implement getArea ไม่งั้น error
getArea(): number {
return Math.PI * this.radius ** 2;
}
}
const circle = new Circle(5);
console.log(circle.describe()); // ใช้ describe จาก Shape ได้
new Shape(); // ❌ Error! สร้าง instance ของ abstract class ไม่ได้ประโยชน์: Shape รับประกันว่า "ทุกรูปทรงต้องมี getArea()" — class ลูกที่ลืม implement จะ error ทันที
Part 8: implements (สัญญาว่าจะมีตามรูปแบบ) — สัญญากับ Interface
📎 คำศัพท์:
implements= "สัญญาว่าจะมีตามรูปแบบ" ของ interface ส่วนextends= "สืบทอด" จาก class แม่ (รายละเอียดของความต่างอยู่ท้าย Part นี้)
class ... implements Interface คือการบอกว่า "class นี้สัญญาว่าจะมีทุกอย่างตามที่ interface ระบุ":
typescript
interface Drawable {
draw(): void;
}
interface Resizable {
resize(scale: number): void;
}
// class หนึ่งตัว implement ได้หลาย interface
class Rectangle implements Drawable, Resizable {
constructor(private width: number, private height: number) {}
draw(): void {
console.log(`วาดสี่เหลี่ยม ${this.width}x${this.height}`);
}
resize(scale: number): void {
this.width *= scale;
this.height *= scale;
}
}ถ้า Rectangle ลืม implement method ใด method หนึ่งที่ interface ระบุ — TypeScript จะ error
extends vs implements ต่างกันยังไง?
extends— สืบทอดของจริง จาก class แม่ (ได้ทั้ง type และโค้ด implementation) สืบทอดได้แค่ class เดียวimplements— แค่ สัญญาว่าจะมีตามรูปแบบ ของ interface (ไม่ได้โค้ดอะไรมา class ต้องเขียนเอง) แต่ implement ได้หลาย interface
Part 9: Generic Class
จำบทที่ 05 ได้ไหม — class ใช้ generic ได้ ทำให้เป็น "โครงที่ทำงานกับ type อะไรก็ได้":
typescript
class Box<T> {
#content: T;
constructor(content: T) {
this.#content = content;
}
getContent(): T {
return this.#content;
}
setContent(content: T): void {
this.#content = content;
}
}
const numberBox = new Box<number>(42);
const textBox = new Box<string>("สวัสดี");
numberBox.setContent(100); // ✅
numberBox.setContent("ผิด"); // ❌ Error! box นี้รับแค่ numberใส่ constraint ให้ generic ของ class ก็ได้ (เหมือนบทที่ 05):
typescript
class Repository<T extends { id: number }> {
#items: T[] = [];
add(item: T): void {
this.#items.push(item);
}
findById(id: number): T | undefined {
return this.#items.find(item => item.id === id);
}
}T extends { id: number } บังคับว่า "ของที่เก็บใน Repository ต้องมี id" — เพราะ findById ต้องใช้ item.id
Part 10: เรื่องที่ต้องระวัง — Property Initialization
ในบทที่ 01 เราเปิด strict ซึ่งรวม strictPropertyInitialization — มันบังคับว่า "ทุก property ต้องถูกกำหนดค่า":
typescript
class User {
name: string; // ❌ Error! Property 'name' has no initializer
}วิธีจัดการ มี 3 ทาง:
typescript
class User {
name: string = "ไม่มีชื่อ"; // วิธีที่ 1 — ใส่ค่าเริ่มต้น
email?: string; // วิธีที่ 2 — ทำให้ optional (มีหรือไม่มีก็ได้)
id!: number; // วิธีที่ 3 — ใส่ ! บอก "เชื่อฉัน เดี๋ยวมีค่าแน่"
constructor(name: string) {
this.name = name;
}
}วิธีที่ดีที่สุดคือกำหนดค่าใน constructor (เหมือน Part 1) หรือใช้ parameter property (Part 2) — ส่วน ! (definite assignment assertion = "ยืนยันว่าจะมีค่าแน่") ใช้เฉพาะเมื่อมั่นใจจริง ๆ ว่า property จะถูกกำหนดค่าด้วยวิธีอื่น (เช่น framework กำหนดให้) — ใช้บ่อยจะกลายเป็นการ "ปิดปาก" TypeScript โดยไม่จำเป็น
💡 Decorators (มัณฑนากร) — ฟีเจอร์เพิ่มเติมที่ใช้ใน framework อย่าง NestJS และ MobX มาตรฐาน TC39 (คณะกรรมการกำหนดมาตรฐาน JavaScript) stage 3 (ขั้นที่ 3 จาก 4 — ใกล้จะเป็นมาตรฐานแล้ว) ณ ปี 2026 — ถ้าไม่ได้ใช้ NestJS หรือ MobX ไม่ต้องสนใจตอนนี้
Part 11: Class หรือไม่ใช้ Class — มุมมองปี 2026
มือใหม่หลายคนเข้าใจว่า "เขียนโปรแกรมต้องใช้ class" — ความจริงในโลก JavaScript/TypeScript ไม่ใช่แบบนั้น
ลองดูสองวิธีทำสิ่งเดียวกัน — ตัวนับเลข:
typescript
// วิธีที่ 1 — ใช้ class
class Counter {
#count = 0;
increment(): number { return ++this.#count; }
getValue(): number { return this.#count; }
}
// วิธีที่ 2 — ใช้ฟังก์ชัน + closure (ไม่มี class)
// (ดู gloss closure ใต้บล็อกโค้ดนี้)
function createCounter() {
let count = 0;
return {
increment: () => ++count,
getValue: () => count,
};
}
const counter = createCounter();
counter.increment();
console.log(counter.getValue()); // 1📎 คำศัพท์: closure (โคลเชอร์) = ฟังก์ชันที่ "จำ" ตัวแปรในขอบเขตด้านนอกที่มันถูกสร้างขึ้นได้ แม้จะถูกเรียกทีหลัง — ในตัวอย่างนี้คือ
countที่ฟังก์ชันincrement/getValueยังเข้าถึงได้ทั้งที่อยู่ "ในตัวแปร local ของ createCounter" (ถ้ายังไม่แม่น ทบทวนได้ในเล่ม Javascript)
ทั้งสองวิธีทำงานเหมือนกัน วิธีที่ 2 (closure) มีข้อดีคือ ไม่ต้องปวดหัวกับ this ซึ่งใน JavaScript มีพฤติกรรมซับซ้อน
เมื่อไหร่ควรใช้ class:
- งานที่ต้องสืบทอด (inheritance) จริง ๆ
- framework บางตัวออกแบบมารอบ class (เช่น Angular, ORM อย่าง TypeORM)
- Domain entity ในสถาปัตยกรรมแบบ DDD
📎 DDD (Domain-Driven Design) คือแนวทางออกแบบซอฟต์แวร์ที่เน้นให้โครงสร้างโค้ดสะท้อนตรรกะของธุรกิจ เช่น class
Order,Customer,Invoiceตรงกับ "ของจริง" ในธุรกิจ ตรงนี้เป็นเรื่องขั้นสูงมาก มือใหม่ข้ามได้เลย ไม่จำเป็นต้องรู้สำหรับ TypeScript ทั่วไป เดี๋ยวค่อยไปเจอตอนทำงานองค์กรใหญ่
เมื่อไหร่ใช้ฟังก์ชัน/object ก็พอ:
- งานทั่วไปส่วนใหญ่ — โดยเฉพาะใน React สมัยใหม่ที่ใช้ function component + hooks
🎯 ข้อสรุป: class เป็นเครื่องมือหนึ่ง ไม่ใช่เครื่องมือเดียว — รู้จักมันไว้ เข้าใจมันให้ดี แต่อย่าคิดว่าทุกอย่างต้องเป็น class เลือกใช้ตามความเหมาะสม
Part 12: Lab — ลงมือทำ
Lab 1 — class พร้อม encapsulation
ฝึกออกแบบ class ที่ซ่อนข้อมูลภายใน (private #items) เปิดให้แตะผ่าน method/getter เท่านั้น พร้อม validation — เห็นภาพ encapsulation ที่ดี (ภายนอกแก้ข้อมูลตรง ๆ ไม่ได้):
typescript
class ShoppingCart {
#items: { name: string; price: number }[] = [];
addItem(name: string, price: number): void {
if (price < 0) throw new Error("ราคาติดลบไม่ได้");
this.#items.push({ name, price });
}
get total(): number {
return this.#items.reduce((sum, item) => sum + item.price, 0);
}
get count(): number {
return this.#items.length;
}
}
const cart = new ShoppingCart();
cart.addItem("หนังสือ", 350);
cart.addItem("ปากกา", 25);
console.log(`มี ${cart.count} ชิ้น รวม ${cart.total} บาท`);Lab 2 — Inheritance + abstract
ฝึกใช้ abstract class — กำหนด "แม่แบบ" ที่บังคับให้ class ลูก implement method บางตัว (abstract) แต่แชร์ logic ที่เหมือนกันได้ เห็นภาพการออกแบบ inheritance ที่บังคับสัญญา:
typescript
abstract class Employee {
constructor(public name: string) {}
abstract calculateSalary(): number;
describe(): string {
return `${this.name} — เงินเดือน ${this.calculateSalary()} บาท`;
}
}
class FullTimeEmployee extends Employee {
constructor(name: string, private monthlySalary: number) {
super(name);
}
override calculateSalary(): number {
return this.monthlySalary;
}
}
class HourlyEmployee extends Employee {
constructor(name: string, private hourlyRate: number, private hours: number) {
super(name);
}
override calculateSalary(): number {
return this.hourlyRate * this.hours;
}
}
const staff: Employee[] = [
new FullTimeEmployee("Alice", 30000),
new HourlyEmployee("Bob", 200, 160),
];
staff.forEach(e => console.log(e.describe()));Lab 3 — Generic class
typescript
class TypedStorage<T> {
#data = new Map<string, T>();
set(key: string, value: T): void {
this.#data.set(key, value);
}
get(key: string): T | undefined {
return this.#data.get(key);
}
has(key: string): boolean {
return this.#data.has(key);
}
}
const userStore = new TypedStorage<{ name: string; age: number }>();
userStore.set("u1", { name: "Alice", age: 30 });
console.log(userStore.get("u1")?.name); // "Alice"Part 13: Checkpoint
1. Parameter property ทำอะไร?
เป็นไวยากรณ์ย่อของ TypeScript — ใส่ access modifier (public/private/...) หน้าพารามิเตอร์ใน constructor แล้วมันจะประกาศ property + assign ค่าให้อัตโนมัติ ไม่ต้องเขียน this.x = x เอง
2. Access modifier 4 ตัวต่างกันยังไง?
public เข้าถึงได้ทุกที่ private เฉพาะใน class นั้น protected ใน class นั้น + class ลูก readonly อ่านได้ทุกที่แต่แก้ไม่ได้
3. private ของ TypeScript ต่างจาก # ยังไง?
private ของ TypeScript มีผลแค่ compile-time (runtime ยังเข้าถึงได้ผ่าน as any) ส่วน # เป็น private จริงของ JavaScript ป้องกันได้ทั้ง compile และ runtime — ปี 2026 แนะนำใช้ #
4. Abstract class คืออะไร?
class ที่เป็น "แม่แบบ" — สร้าง instance ตรง ๆ ไม่ได้ มี abstract method ที่ประกาศไว้แต่ไม่มี body บังคับให้ class ลูกมา implement
5. extends กับ implements ต่างกันยังไง?
extends = สืบทอดของจริงจาก class แม่ (ได้ทั้ง type และโค้ด, ได้แค่ class เดียว) implements = สัญญาว่าจะมีตามรูปแบบ interface (ไม่ได้โค้ด ต้องเขียนเอง, ได้หลาย interface)
6. ทำไมควรใส่คำว่า override?
เมื่อเปิด noImplicitOverride การใส่ override ทำให้ — ถ้ามีคนเปลี่ยนชื่อ method ใน class แม่ method ที่ override ใน class ลูกจะ error ทันที ช่วยให้ refactor ปลอดภัย
7. ปี 2026 ต้องใช้ class กับทุกอย่างไหม?
ไม่ — class เป็นเครื่องมือหนึ่ง งานทั่วไปจำนวนมากใช้ฟังก์ชัน + object/closure ก็พอ (และไม่ต้องปวดหัวกับ this) ใช้ class เมื่อต้องสืบทอดจริง ๆ หรือ framework บังคับ
Part 14: สรุปบทนี้
- TypeScript เพิ่ม type และ modifier ให้ class
- Parameter property — ใส่ modifier หน้าพารามิเตอร์ constructor = ประกาศ + assign อัตโนมัติ
- Access modifier —
public/private/protected/readonlyใช้ทำ encapsulation privateของ TS = compile-time เท่านั้น,#= private จริง (แนะนำใช้#)- Inheritance —
extends,super,override - Abstract class — แม่แบบที่บังคับ class ลูก implement
implements— สัญญากับ interface (ได้หลายตัว)- Generic class — โครงที่ทำงานกับ type อะไรก็ได้
- class ไม่ใช่คำตอบของทุกอย่าง — เลือกใช้ตามความเหมาะสม
บทถัดไป — Module และ Declaration File