JavaScript
Class
Getter / Setter
เรียนรู้ getter และ setter — method พิเศษที่ใช้เหมือน property — ตั้งแต่ computed property, validation, encapsulation, read-only getter, inheritance ไปจนถึงการใช้งานร่วมกับ private fields — ผ่าน Lab 4 ข้อ
Getter คืออะไร — method ที่เรียกโดยไม่ต้องใส่วงเล็บ
**Getter** คือ method พิเศษใน class ที่ใช้ keyword `get` นำหน้า — เมื่อเรียกใช้ ไม่ต้องใส่วงเล็บ `()` — ใช้ syntax `obj.propertyName` เหมือนเข้าถึง property ปกติ ต่างจาก method ปกติที่ต้องเรียก `obj.methodName()` — getter ทำให้โค้ดอ่านง่ายขึ้นและซ่อน logic การคำนวณไว้ข้างหลัง **วิธีคิด**: getter = method ที่ปลอมตัวเป็น property — ภายนอกใช้เหมือน property — ข้างในรัน function จริง
`get fullName()` — ไม่มี parameter — คืนค่า string ที่ต่อจาก `firstName` และ `lastName` — เรียกใช้ด้วย `user.fullName` (ไม่มีวงเล็บ)
class User {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
get fullName() {
return this.firstName + " " + this.lastName;
}
}
const user = new User("สมชาย", "ใจดี");
console.log(user.fullName); // "สมชาย ใจดี"
// ❌ เรียกแบบมีวงเล็บจะ error
// console.log(user.fullName()); // TypeError: user.fullName is not a functionจากตัวอย่าง: - `get fullName()` — ประกาศ getter ด้วย keyword `get` — **ห้ามมี parameter** - `user.fullName` — เรียก getter โดยไม่ต้องใช้วงเล็บ — เหมือนอ่าน property - ภายใน getter — `this.firstName` และ `this.lastName` คือ property ของ instance — getter เข้าถึงได้ตามปกติ - ถ้าเรียก `user.fullName()` — JavaScript จะ error เพราะ getter ไม่ใช่ function — มันคือ property **ข้อสังเกต**: Getter ไม่เก็บค่าเอง — มัน**คำนวณค่าใหม่ทุกครั้งที่เรียก** — ถ้า `firstName` หรือ `lastName` เปลี่ยน — `fullName` ที่อ่านครั้งต่อไปจะเปลี่ยนตามอัตโนมัติ
Setter คืออะไร — ดักจับการกำหนดค่าด้วย `=`
**Setter** คือ method พิเศษที่ใช้ keyword `set` นำหน้า — มันจะทำงานอัตโนมัติทุกครั้งที่มีการกำหนดค่าด้วย `=` `obj.property = value` — JavaScript จะเรียก setter แทนที่จะเขียน property โดยตรง — ทำให้เราใส่ logic ตรวจสอบหรือแปลงค่าก่อนเก็บได้ **คู่หู getter + setter**: ใช้ชื่อเดียวกัน — getter อ่านค่า — setter กำหนดค่า — ทำให้ property หนึ่งตัวมีทั้ง logic อ่านและ logic เขียน
`get age()` — อ่าน `this._age` — `set age(value)` — ตรวจสอบก่อนกำหนดค่า — เรียกใช้ `person.age` (อ่าน) และ `person.age = 25` (เขียน)
class Person {
constructor(name, age) {
this.name = name;
this._age = 0; // ใช้ _age เก็บค่าจริง (convention)
this.age = age; // เรียก setter — ผ่าน validation
}
get age() {
return this._age;
}
set age(value) {
if (value < 0) {
console.log("อายุต้องไม่ต่ำกว่า 0");
return; // ไม่เปลี่ยนค่า
}
this._age = value;
}
}
const p = new Person("สมชาย", 25);
console.log(p.age); // 25
p.age = 30;
console.log(p.age); // 30
p.age = -5; // "อายุต้องไม่ต่ำกว่า 0"
console.log(p.age); // 30 — ค่าเดิมไม่เปลี่ยนจากตัวอย่าง: - `get age()` — getter — ใช้ `p.age` อ่านค่า (ไม่มีวงเล็บ) - `set age(value)` — setter — ใช้ `p.age = 30` กำหนดค่า (เรียก setter อัตโนมัติ) - **Setter ต้องมี parameter 1 ตัวเสมอ** — `value` คือค่าที่ถูกกำหนดจาก `=` - `this.age = age` ใน constructor — เรียก setter ด้วย — ทำให้ validation ทำงานตั้งแต่สร้าง instance - ตอน `p.age = -5` — setter ตรวจเจอค่าไม่ถูกต้อง — `return` โดยไม่เปลี่ยน `this._age` — ป้องกันค่าเสีย **ข้อควรระวัง**: ตัวอย่างนี้ใช้ `_age` (underscore convention) เก็บค่าจริง — อย่าใช้ชื่อ `age` ซ้ำกับ getter/setter เพราะจะทำให้เกิด **infinite recursion** (เรียกตัวเองวนลูปไม่สิ้นสุด)
ทำไมต้องใช้ getter/setter — 3 use-case หลัก
Getter/setter มีประโยชน์ใน 3 สถานการณ์หลัก: 1. **Computed property** — ค่าที่คำนวณจาก field อื่น — เช่น `fullName` จาก `firstName + lastName` — `area` จาก `radius` 2. **Validation** — ตรวจสอบข้อมูลก่อนกำหนดค่า — เช่น กันอายุติดลบ กันราคาติดลบ กันคะแนนเกิน 100 3. **Encapsulation** — ซ่อนโครงสร้างภายใน — ให้ภายนอกเห็น interface เดียว (`obj.price`) — แต่ภายในใช้ `#price` เก็บค่าจริง พร้อม logic ตรวจสอบ
`#celsius` เก็บค่าจริง (encapsulation) `get fahrenheit()` — computed property — แปลง C → F `set fahrenheit(value)` — รับค่า F — แปลงเป็น C และ validation
class Temperature {
#celsius = 0; // encapsulation: เก็บเป็น Celsius เสมอ
constructor(celsius) {
this.#celsius = celsius;
}
// getter — computed: แปลง C → F
get fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
// setter — computed + validation: แปลง F → C
set fahrenheit(value) {
const celsius = (value - 32) * 5 / 9;
if (celsius < -273.15) {
console.log("อุณหภูมิต่ำกว่า absolute zero ไม่ได้");
return;
}
this.#celsius = celsius;
}
get celsius() {
return this.#celsius;
}
}
const t = new Temperature(25);
console.log(t.fahrenheit); // 77 — computed: 25°C → 77°F
t.fahrenheit = 32; // กำหนด 32°F → แปลงเป็น 0°C
console.log(t.celsius); // 0
t.fahrenheit = -500; // "อุณหภูมิต่ำกว่า absolute zero ไม่ได้"
console.log(t.celsius); // 0 — ค่าเดิมไม่เปลี่ยนตัวอย่างนี้รวม 3 use-case ในคลาสเดียว: - **Encapsulation**: `#celsius` — ภายในเก็บเป็น Celsius ตลอด — ภายนอกไม่รู้โครงสร้างภายใน - **Computed property**: `get fahrenheit()` — คำนวณ Fahrenheit จาก `#celsius` — ภายนอกอ่าน `t.fahrenheit` ได้เหมือน property ปกติ - **Validation**: `set fahrenheit()` — แปลง F → C — ตรวจสอบไม่ต่ำกว่า absolute zero ก่อนกำหนดค่า **ประโยชน์**: ภายนอกใช้ `t.fahrenheit` เหมือน property ธรรมดา — ไม่ต้องรู้ว่าข้างในเก็บเป็น Celsius — และ validation ป้องกันค่าผิดพลาด
Getter/setter + private fields — pattern ซ่อนข้อมูล เปิด interface สวย ๆ
ในบท Private Fields (#) คุณได้เห็น getter/setter ใช้ร่วมกับ `#field` ไปแล้ว — มาดูให้ละเอียดขึ้น **Pattern นี้คือการใช้ getter/setter ที่มีประสิทธิภาพที่สุด**: - `#field` — เก็บค่าจริง — ไม่มีใครเข้าถึงจากภายนอก - `get field()` — เปิดให้อ่าน — ด้วย syntax `obj.field` - `set field(value)` — เปิดให้เขียน — พร้อม validation ภายนอกใช้ `obj.field` เหมือน property ปกติ — แต่ข้างในมีทั้ง private data, logic การอ่าน, และ validation — **ปลอดภัยและใช้งานง่ายในเวลาเดียวกัน**
`#price` — private 100% `get price()` — อ่านค่า `set price()` — ตรวจสอบก่อนกำหนดค่า
class Product {
#price = 0; // private — กันการเข้าถึงตรง
constructor(name, price) {
this.name = name; // public field
this.price = price; // เรียก setter — ผ่าน validation
}
get price() {
return this.#price; // ✅ อ่าน #price ผ่าน getter
}
set price(value) {
if (value < 0) {
console.log("ราคาต้องไม่ต่ำกว่า 0");
return; // ❌ ไม่กำหนดค่า — ป้องกันราคาติดลบ
}
this.#price = value; // ✅ กำหนดค่าเมื่อผ่าน validation
}
}
const p = new Product("เมาส์", 590);
console.log(p.price); // 590 — ✅ อ่าน
p.price = 999; // ✅ เขียน — ผ่าน validation
console.log(p.price); // 999
p.price = -100; // "ราคาต้องไม่ต่ำกว่า 0"
console.log(p.price); // 999 — ค่าเดิมไม่เปลี่ยน
// ❌ p.#price — SyntaxError — ภายนอกเข้าไม่ถึง #price**Pattern นี้ให้อะไร**: - `p.price` — ใช้เหมือน property ปกติ — สะดวก - `#price` — ไม่มีใครเปลี่ยนโดยตรง — ปลอดภัย - `set price()` — validation อัตโนมัติ — ป้องกันค่าผิดพลาด - `get price()` — อ่านค่าผ่าน interface สวย ๆ — ผู้ใช้ไม่ต้องรู้ว่าข้างในคือ `#price` **เทียบกับ private field + method**: ถ้าไม่มี getter/setter — ต้องใช้ `p.getPrice()` และ `p.setPrice(999)` — getter/setter ทำให้ API สะอาดกว่า
Getter อย่างเดียว = read-only property
ถ้ามีแต่ `get` โดยไม่มี `set` — property นั้นจะกลายเป็น **read-only** — อ่านได้อย่างเดียว กำหนดค่าใหม่ตรง ๆ ไม่ได้ **Use-case**: - ค่าที่คำนวณจาก field อื่น — เช่น `area`, `perimeter` — ไม่ควรมีใครมา `set` ทับ - ข้อมูลที่ควรอ่านได้แต่ห้ามแก้จากภายนอก — เช่น `id`, `createdAt` — กำหนดได้ครั้งเดียวใน constructor **ข้อควรรู้**: ถ้าไม่มี setter — `obj.prop = value` ใน strict mode จะ throw `TypeError` — ใน non-strict mode จะ silent fail (ไม่มีผลใด ๆ)
`get area()` คำนวณพื้นที่จาก `#radius` — `get diameter()` คำนวณเส้นผ่านศูนย์กลาง — ทั้งคู่ไม่มี setter → อ่านได้อย่างเดียว
class Circle {
#radius;
constructor(radius) {
this.#radius = radius;
}
get area() {
return Math.PI * this.#radius * this.#radius;
}
get diameter() {
return this.#radius * 2;
}
}
const c = new Circle(5);
console.log(c.area); // 78.53981633974483
console.log(c.diameter); // 10
c.area = 100; // ❌ silent fail (ไม่มี setter — ไม่มีผล)
console.log(c.area); // 78.5398... — ค่าเดิมไม่เปลี่ยน**ข้อสังเกต**: - `c.area` — อ่าน getter — คำนวณ `πr²` จาก `#radius` - `c.area = 100` — พยายามกำหนดค่า — แต่ไม่มี setter — assignment นั้นไม่มีผล (หรือ throw error ใน strict mode) - `diameter` — อีกตัวอย่าง computed property แบบ read-only — `2 * radius` **วิธีคิด**: Getter อย่างเดียว = คำนวณค่าแบบ on-demand จากข้อมูลตั้งต้น — ไม่ควรมีใครมาเขียนทับ — ไม่ต้องใช้ method — อ่านผ่าน property เลย
Getter/setter กับ Inheritance — child class สืบทอดและ override ได้
Getter และ setter เป็นส่วนหนึ่งของ class — **child class สืบทอด getter/setter จาก parent ได้** — และยังสามารถ **override** (เขียนทับ) ด้วย getter/setter เวอร์ชันของตัวเอง ภายใน getter/setter ของ child — ใช้ `super.propertyName` เพื่อเรียก getter/setter ของ parent ได้
`Dog` extends `Animal` — override `get description()` — ต่อท้ายข้อมูลสายพันธุ์ — ใช้ `super.description` เรียก getter ของ parent
class Animal {
constructor(name) {
this.name = name;
}
get description() {
return "Animal: " + this.name;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
// override getter — ต่อท้ายข้อมูลสายพันธุ์
get description() {
return super.description + " (พันธุ์ " + this.breed + ")";
}
}
const d = new Dog("ลัคกี้", "โกลเด้น");
console.log(d.description);
// "Animal: ลัคกี้ (พันธุ์ โกลเด้น)"จากตัวอย่าง: - `Animal` มี getter `description` — `Dog` extends `Animal` - `Dog` override `get description()` — เรียก `super.description` เพื่อใช้ getter ของ parent — แล้วต่อท้ายข้อมูลสายพันธุ์ - `super.description` ใน getter ของ child — ทำงานเหมือน `super.method()` — เรียกเวอร์ชันของ parent **กฎสำคัญ**: - Child override getter/setter ได้ — syntax เหมือน parent — `get propName()` / `set propName(value)` - ใช้ `super.propName` เพื่อเรียก getter/setter ของ parent — ไม่ใช่ `super.getPropName()` - Setter ก็ override ได้เช่นกัน — `set propName(value) { super.propName = value; ... }`
กฎสำคัญของ getter/setter
| กฎ | คำอธิบาย | ตัวอย่าง |
|---|---|---|
| Getter ห้ามมี parameter | `get name()` — ต้องไม่มี parameter — ถ้ามีจะ throw `SyntaxError` | `get fullName() { }` ✅ / `get fullName(x) { }` ❌ |
| Setter ต้องมี parameter เดียว | `set name(value)` — ต้องมี parameter 1 ตัว — มากกว่า 1 ตัวจะ throw `SyntaxError` | `set age(v) { }` ✅ / `set age() { }` ❌ / `set age(a, b) { }` ❌ |
| ห้ามมี data property ชื่อเดียวกับ getter/setter | ถ้า class มี `this.name = ...` อยู่แล้ว — ห้ามประกาศ `get name()` หรือ `set name()` — จะ conflict | `this.name` + `get name()` ❌ / ใช้ `this._name` + `get name()` ✅ |
| Getter ทำงานทุกครั้งที่อ่าน | `obj.prop` — getter จะรัน function ใหม่ทุกครั้ง — ไม่ได้ cache ค่า — ถ้า logic หนัก ควรระวัง | `obj.fullName` — คำนวณใหม่ทุกครั้งที่อ่าน |
| Getter ไม่ใช่ method — ห้ามเรียกแบบ `obj.prop()` | `obj.getterName()` — JavaScript จะ throw `TypeError` เพราะ getter ไม่ใช่ function — มันคือ property | `user.fullName` ✅ / `user.fullName()` ❌ |
| Setter ไม่ return ค่า (หรือ return ถูก ignore) | `set name(value) { ... }` — ค่าที่ return จาก setter จะถูก ignore — ใช้แค่กำหนดค่าภายใน | `set age(v) { return v; }` — return ไม่มีผล |
| Override ได้ด้วย getter/setter | Child class ประกาศ `get prop()` ซ้ำ — override getter ของ parent — ใช้ `super.prop` เรียกของ parent | `get desc() { return super.desc + '...'; }` |
ข้อผิดพลาดที่พบบ่อย
- **Infinite recursion — getter เรียกตัวเอง**: `get name() { return this.name; }` — `this.name` จะเรียก getter ซ้ำ — วนลูปไม่สิ้นสุด → `RangeError: Maximum call stack size exceeded` — **แก้**: ใช้ตัวแปรคนละชื่อ เช่น `this._name` หรือ `this.#name`
- **Setter เรียกตัวเอง**: `set name(value) { this.name = value; }` — `this.name = value` จะเรียก setter ซ้ำ — วนลูปไม่สิ้นสุด — **แก้**: ใช้ `this._name = value` หรือ `this.#name = value`
- **ลืมใส่ parameter ใน setter**: `set name() { ... }` — setter ต้องมี 1 parameter — JavaScript จะ throw `SyntaxError: Setter must have exactly one formal parameter`
- **ใส่ parameter ใน getter**: `get name(value) { ... }` — getter ต้องไม่มี parameter — JavaScript จะ throw `SyntaxError: Getter must not have any formal parameters`
- **ตั้งชื่อ property ซ้ำกับ getter/setter**: `this.name = "test"` ใน constructor และ `get name() { ... }` ใน class — JavaScript จะ conflict — **แก้**: ใช้ `this._name` หรือ `this.#name` เก็บค่าจริง
- **พยายาม assign getter-only property ใน strict mode**: `'use strict'; obj.readOnlyProp = 1;` — จะ throw `TypeError` — เพราะไม่มี setter — ถ้าไม่ strict mode จะ silent fail — ดูไม่ออกว่าทำไมค่าไม่เปลี่ยน
- **เข้าใจผิดว่า getter cache ค่า**: `get random() { return Math.random(); }` — ทุกครั้งที่อ่าน `obj.random` — จะได้ค่าใหม่ — getter ไม่ได้จำค่า — มันรัน function ทุกครั้ง
- **ใช้ `delete` กับ getter/setter property**: `delete obj.prop` — ไม่สามารถลบ property ที่มี getter/setter ได้ — ใช้ `delete` ไม่ได้ผล