Skip to content

บทที่ 06 — Objects, Prototypes และ Class

← บทที่ 05 | สารบัญ | บทที่ 07: Arrays →

Object คือวิธีที่ JavaScript ใช้จัดกลุ่มข้อมูลที่เกี่ยวข้องกัน — เป็นโครงสร้างข้อมูลที่ใช้บ่อยที่สุดในภาษานี้

อ่านจบบทนี้คุณจะ:

  • สร้างและจัดการ object ได้คล่อง
  • เข้าใจ "prototype" — กลไกเบื้องหลังที่ทำให้ object ทำงาน
  • เขียน class เป็น พร้อมเข้าใจว่ามันเป็นแค่ "เปลือก" ของ prototype
  • รู้จัก Map และ Set — โครงสร้างข้อมูลที่บางทีดีกว่า object/array

ใช้เวลา 3 ชั่วโมง


Part 1: Object พื้นฐาน

1.1 Object คืออะไร

Object = กลุ่มของข้อมูลที่เก็บเป็นคู่ "key: value"

ลองนึกถึง "บัตรประชาชน" — มันรวมข้อมูลของคนหนึ่งคน: ชื่อ, อายุ, ที่อยู่ — object ก็ทำแบบเดียวกัน

javascript
const user = {
  name: "Alice",          // key "name" → value "Alice"
  age: 25,                 // key "age" → value 25
  isActive: true,
};

แต่ละบรรทัดเรียกว่า "คุณสมบัติ" (property)name คือ key, "Alice" คือ value

1.2 เข้าถึงข้อมูลใน Object

มี 2 วิธี:

วิธี dot (.) — ใช้บ่อยสุด:

javascript
user.name              // "Alice"
user.age               // 25

วิธี bracket ([ ]) — ใช้เมื่อ key มีอักขระพิเศษ หรือ key เป็นตัวแปร:

javascript
user["name"]           // "Alice"

const key = "age";
user[key]              // 25 — key เป็นตัวแปร ต้องใช้ bracket

const obj = { "my key": 1 };
obj["my key"]          // 1 — key มีช่องว่าง ต้องใช้ bracket

จำง่าย: รู้ชื่อ key แน่นอน → . / key เป็นตัวแปร หรือมีอักขระพิเศษ → [ ]

1.3 เพิ่ม / แก้ / ลบ property

object ใน JS เปลี่ยนแปลงได้ตลอด — เพิ่ม property ใหม่ด้วยการกำหนดค่า, แก้ค่าเดิมด้วยการกำหนดทับ, และลบด้วย delete (สังเกตว่าแม้ประกาศด้วย const ก็ยังแก้ property ภายในได้ เพราะ const ล็อกแค่การอ้างอิง):

📝 หมายเหตุ: delete เป็น operator พิเศษของภาษา ไม่ใช่ function — ใช้ลบ property ของ object เท่านั้น (ไม่ใช้ลบตัวแปร)

javascript
const user = { name: "Alice" };

user.age = 25;             // เพิ่ม property ใหม่
user.name = "Bob";          // แก้ค่าเดิม
delete user.age;            // ลบ property (delete เป็น operator)

console.log("name" in user);   // true — เช็คว่ามี key นี้ไหม

1.4 Method — function ใน object

property ที่เป็น function เรียกว่า "method":

javascript
const user = {
  name: "Alice",
  greet() {                          // method
    return "สวัสดี " + this.name;     // this = object ตัวเอง (จำบทที่ 05)
  },
};

user.greet();          // "สวัสดี Alice"

(จำเรื่อง this จากบทที่ 05 — ใน method ปกติ this คือ object ที่เรียก)

1.5 Property Shorthand — เขียนสั้น

เวลาสร้าง object จากตัวแปรที่ชื่อตรงกับ key อยู่แล้ว JavaScript มีรูปย่อให้เขียนแค่ชื่อครั้งเดียว ({ name } แทน { name: name }) — ลดความซ้ำซ้อน เจอบ่อยมากในโค้ดจริง:

ถ้าชื่อตัวแปรตรงกับชื่อ key:

javascript
const name = "Alice";
const age = 25;

// แบบยาว
const user1 = { name: name, age: age };

// แบบสั้น (shorthand) — เหมือนกัน
const user2 = { name, age };

1.6 Computed Property — key เป็นตัวแปร

ใช้ [ ] ตอนสร้าง object เพื่อให้ key มาจากตัวแปร:

javascript
const key = "status";
const value = "active";

const obj = {
  [key]: value,            // key มาจากตัวแปร
};
console.log(obj);          // { status: "active" }

Part 2: วน Object

จากบทที่ 04 เราเคยเจอ for...in ที่วน key ของ object ได้ แต่ for...in วนรวม property ที่มาจาก prototype ด้วย (ซึ่งมักไม่ใช่สิ่งที่เราอยากได้) ในงานจริงจึงแนะนำให้ใช้ Object.keys / Object.values / Object.entries แทน เพราะสามคำสั่งนี้คืนแค่ property ที่ object สร้างขึ้นเองเท่านั้น ไม่รวมของที่สืบทอดมาจาก prototype

📝 กฎง่าย ๆ สำหรับตอนนี้: ใช้ Object.keys / Object.values / Object.entries แทน for...in เสมอเมื่อวน object — ปลอดภัยกว่าเสมอไม่ว่า object จะซับซ้อนแค่ไหน ไม่ต้องกังวลเรื่อง prototype chain (เดี๋ยวเราจะอธิบายเรื่องนี้ใน Part 4)

2.1 Object.keys / values / entries

javascript
const user = { name: "Alice", age: 25, city: "Bangkok" };

Object.keys(user)        // ["name", "age", "city"]  — array ของ key
Object.values(user)      // ["Alice", 25, "Bangkok"] — array ของ value
Object.entries(user)     // [["name","Alice"], ["age",25], ["city","Bangkok"]]

เมื่อแปลงเป็น array แล้ว — วนด้วย for...of ได้ (ดีกว่า for...in):

javascript
for (const key of Object.keys(user)) {
  console.log(key, "=", user[key]);
}

// หรือแกะ key+value พร้อมกันด้วย destructuring
for (const [key, value] of Object.entries(user)) {
  console.log(key, "=", value);
}

2.2 Object.fromEntries — ตรงข้ามกับ entries

javascript
const entries = [["name", "Alice"], ["age", 25]];
const obj = Object.fromEntries(entries);
console.log(obj);        // { name: "Alice", age: 25 }

มีประโยชน์ตอน "แปลง object → array → ทำอะไรบางอย่าง → แปลงกลับ":

javascript
const prices = { apple: 30, banana: 15, orange: 25 };

// เพิ่มราคาทุกตัว 10%
const newPrices = Object.fromEntries(
  Object.entries(prices).map(([name, price]) => [name, price * 1.1])
);

📝 ตัวอย่างนี้ใช้ .map() ของ array — ถ้ายังไม่คุ้น ดูบทที่ 07 ที่เราอธิบาย map/filter/reduce ละเอียด ตรงนี้เห็นภาพคร่าว ๆ ว่า "แปลงทุกตัว" ก็พอ


Part 3: Copy Object (ทบทวน + เพิ่ม)

จากบทที่ 02 — object copy แบบ reference. ทบทวนวิธี copy:

javascript
const original = { name: "Alice", age: 25 };

// shallow copy (copy ตื้น — ชั้นเดียว)
const copy1 = { ...original };
const copy2 = Object.assign({}, original);

// deep copy (copy ลึกทุกชั้น)
const copy3 = structuredClone(original);

3.1 Object.freeze — ทำให้ object เปลี่ยนแปลงไม่ได้

จากบทที่ 02 — const ไม่ได้ห้ามแก้ข้างใน object. ถ้าอยากห้ามแก้จริง ๆ ใช้ Object.freeze:

javascript
const config = Object.freeze({
  apiUrl: "https://api.example.com",
  timeout: 5000,
});

config.timeout = 1000;          // ❌ TypeError ใน strict mode / ES module
console.log(config.timeout);    // 5000 — ไม่เปลี่ยน

⚠️ โหมดสำคัญ: ใน strict mode หรือ ES module (ซึ่งเป็นค่า default ของโค้ดสมัยใหม่) การกำหนดค่าใหม่ (assign) ให้ property ของ object ที่ freeze แล้ว จะ throw TypeError ทันที — ไม่ใช่เงียบเหมือนสมัยก่อน (sloppy mode เก่าเท่านั้นที่เงียบ) ดังนั้นถ้าอยู่ในไฟล์ .mjs, ใน <script type="module">, หรือมี "use strict" → จะได้ error

ใช้กับค่าคงที่ที่ไม่อยากให้ใครเผลอแก้ — error ที่ throw ออกมาช่วยจับบั๊กได้เร็วขึ้น


Part 4: Prototype (ต้นแบบ) — กลไกเบื้องหลัง Object

🚀 โซนขั้นสูง — ข้ามได้ (แต่ดีถ้าอ่าน)

prototype = ต้นแบบ — object อีกตัวที่ object ของเราเชื่อมไปหา เพื่อยืม property/method มาใช้

พูดง่าย ๆ: prototype คือ "object แม่แบบ" ที่ object ลูกสามารถยืม method มาใช้ได้ — เหมือนเด็กที่ยืมทักษะจากพ่อแม่โดยที่ไม่ต้องก็อปทุกอย่างมาเก็บเอง

เรื่อง prototype (ต้นแบบ) เป็นกลไก "เบื้องหลัง" ที่มือใหม่ไม่ได้เขียนตรง ๆ — ในงานจริงเราใช้ class (Part 5) ซึ่งจัดการ prototype ให้แล้ว ถ้าอ่านแล้วงง ข้ามไป Part 5 ได้เลย แล้วค่อยกลับมาเข้าใจลึกทีหลัง ขอแค่รู้ว่า "class เบื้องหลังคือ prototype (ต้นแบบ)" ก็พอ

นี่คือส่วนที่ลึกที่สุดของบท — เป็นกลไกที่ทำให้ object ของ JavaScript ทำงาน

4.1 ปัญหา — method ที่ใช้ร่วมกัน

ลองคิด: ถ้าเรามี user 1000 คน แต่ละคนมี method greet เหมือนกัน:

javascript
const user1 = { name: "Alice", greet() { return "Hi " + this.name; } };
const user2 = { name: "Bob",   greet() { return "Hi " + this.name; } };
// ... 1000 คน

เก็บ greet 1000 ชุดที่เหมือนกันเป๊ะ — เปลือง memory

→ ต้องการ "ที่เก็บ method ส่วนกลาง" ที่ทุก object ใช้ร่วมกัน — นั่นคือ prototype

4.2 ห่วงโซ่ต้นแบบ (Prototype Chain)

ทุก object ใน JavaScript มี "ตัวเชื่อม" ลับ ๆ ที่ชี้ไปยัง object อีกตัว เรียกว่า prototype (ต้นแบบ)

เมื่อเราเรียก property/method ของ object — JavaScript หาแบบนี้:

  1. หาใน object ตัวเองก่อน — เจอ → ใช้เลย
  2. ไม่เจอ → ไปหาใน prototype ของมัน
  3. ยังไม่เจอ → ไปหาใน prototype ของ prototype
  4. ... ไปเรื่อย ๆ จนสุดทาง (เป็น null)

ลำดับนี้เรียกว่า ห่วงโซ่ต้นแบบ (prototype chain)

javascript
const arr = [1, 2, 3];

arr.map(...)
// JavaScript หา .map ในตัว arr → ไม่เจอ
// → ไปหาใน Array.prototype → เจอ! → ใช้

นี่คือเหตุผลที่ array ทุกตัวมี .map, .filter — เพราะ method เหล่านี้อยู่ใน Array.prototype ที่ array ทุกตัวเชื่อมไปถึง

💡 เปรียบเทียบ: prototype chain เหมือน "ถามต่อ ๆ กัน" — ถามตัวเอง ไม่รู้ → ถามพ่อแม่ ไม่รู้ → ถามปู่ย่า — จนกว่าจะเจอคำตอบ หรือจนไม่มีใครให้ถามต่อแล้ว

4.3 ดู prototype

javascript
const obj = {};
Object.getPrototypeOf(obj) === Object.prototype;     // true

const arr = [];
Object.getPrototypeOf(arr) === Array.prototype;       // true

ตอนนี้แค่ "เข้าใจว่ามันมี" ก็พอ — ในงานจริงเราไม่ค่อยยุ่งกับ prototype ตรง ๆ เพราะเราใช้ class แทน (ซึ่งจัดการ prototype ให้เรา)


Part 5: Class — เปลือกที่สวยงามของ Prototype

📌 สรุปสั้น ๆ ของ prototype (เผื่อยังไม่ชัวร์จาก Part 4): prototype = "ต้นแบบ" — object อีกตัวที่ object ของเราเชื่อมไปหาเพื่อยืม method ใช้ร่วมกัน เวลาเขียน class แล้วประกาศ method ใน class — ภายในแล้ว JavaScript เก็บ method นั้นไว้ที่ prototype เพื่อให้ทุก instance ใช้ร่วมกัน (ประหยัด memory)

class เป็น syntax ที่ ES6 เพิ่มเข้ามา — มันคือ "วิธีเขียนที่อ่านง่าย" ของระบบ prototype

💡 ทำไมต้องใช้ class? ลองนึกภาพต้องสร้าง user 100 คน — ถ้าเขียน object literal 100 ชุดด้วยมือ จะซ้ำซากและเสียเวลามาก ด้วย class เราเขียน "พิมพ์เขียว" ครั้งเดียว แล้วใช้ new สร้าง instance ได้กี่ตัวก็ได้ — code สั้นกว่า บำรุงรักษาง่ายกว่า และ method ทุก instance ใช้ร่วมกันจาก prototype (ประหยัด memory)

5.1 Class พื้นฐาน

javascript
class User {
  constructor(name, age) {        // constructor — ทำงานตอนสร้าง object
    this.name = name;
    this.age = age;
  }

  greet() {                        // method
    return "สวัสดี " + this.name;
  }

  haveBirthday() {
    this.age++;
  }
}

// สร้าง object จาก class ด้วย new
const alice = new User("Alice", 25);

alice.greet();           // "สวัสดี Alice"
alice.haveBirthday();
console.log(alice.age);  // 26

อธิบาย:

  • class User — ประกาศ class ชื่อ User (เหมือน "พิมพ์เขียว")
  • constructor — function พิเศษ ทำงานตอน new — ใช้ตั้งค่าเริ่มต้น
  • new User(...) — สร้าง object จริง (เรียก "instance" = ตัวอย่างจริงที่สร้างจาก class) จากพิมพ์เขียว
  • method ที่เขียนใน class จะอยู่ใน prototype → ทุก instance ใช้ร่วมกัน (แก้ปัญหา Part 4.1)

💡 เปรียบเทียบ: class = "พิมพ์เขียวบ้าน" — instance (จาก new) = "บ้านจริงที่สร้างจากพิมพ์เขียว" — พิมพ์เขียวเดียวสร้างบ้านได้หลายหลัง

5.2 Class Field — ประกาศ property ตรง ๆ

นอกจากกำหนด property ใน constructor เราประกาศ property พร้อมค่าเริ่มต้นไว้ในตัว class ตรง ๆ ได้เลย (class field) — อ่านง่ายและเหมาะกับค่าที่มี default ชัดเจน:

javascript
class Counter {
  count = 0;                       // class field — ค่าเริ่มต้น
  step = 1;

  increment() {
    this.count += this.step;
  }
}

const c = new Counter();
c.increment();
console.log(c.count);    // 1

5.3 Private Field — # (ข้อมูลส่วนตัว)

ใส่ # หน้าชื่อ → property/method นั้นเข้าถึงได้แค่ในตัว class:

javascript
class BankAccount {
  #balance = 0;                    // private — ข้างนอกแตะไม่ได้

  deposit(amount) {
    this.#balance += amount;
  }

  getBalance() {
    return this.#balance;
  }
}

const acc = new BankAccount();
acc.deposit(100);
console.log(acc.getBalance());     // 100
// console.log(acc.#balance);      // ❌ บรรทัดนี้ comment เพราะถ้า uncomment จะทำให้ทั้งไฟล์รันไม่ได้เลย
//                                 //   (ภาษาห้าม syntax `obj.#field` นอก class body
//                                 //   ตั้งแต่ขั้นแปลงโค้ด ไม่ใช่ตอนรัน — สคริปต์ไม่ทันได้รันเลย)

⚠️ บรรทัด acc.#balance ถูก comment ไว้เพราะถ้าไม่ comment — ทั้งไฟล์จะ parse ไม่ผ่าน (SyntaxError) ตั้งแต่ก่อนรันบรรทัดอื่นใด ไม่ใช่ error ตอน runtime ทดลองโดย uncomment ดูได้ — engine จะปฏิเสธทั้งสคริปต์ตั้งแต่ขั้นโหลด

#balance ทำหน้าที่เดียวกับ closure ใน "createBankAccount" ของบทที่ 05 — แต่เขียนด้วย class

5.4 Static — property/method ของตัว class เอง

static ทำให้ property/method เป็นของ "class" ไม่ใช่ของ "instance":

javascript
class MathHelper {
  static PI = 3.14159;

  static square(n) {
    return n * n;
  }
}

MathHelper.PI;             // 3.14159 — เรียกจาก class ตรง ๆ
MathHelper.square(5);      // 25

const m = new MathHelper();
m.square(5);               // ❌ Error — static ไม่ได้อยู่ใน instance

ใช้ static กับสิ่งที่ "เกี่ยวกับ class แต่ไม่ผูกกับ instance ใดตัวหนึ่ง" เช่น ค่าคงที่, factory method (= static method ที่สร้างและคืน instance ออกมา — เช่น User.fromJSON(...))

5.5 Getter / Setter — property ที่มี logic

get/set ทำให้เราเขียน method แต่เรียกใช้เหมือน property:

javascript
class Temperature {
  #celsius = 0;

  get celsius() {                  // getter — เรียกเหมือน property
    return this.#celsius;
  }

  set celsius(value) {              // setter
    if (value < -273.15) throw new Error("ต่ำกว่าศูนย์สัมบูรณ์");
    this.#celsius = value;
  }

  get fahrenheit() {                // getter ที่คำนวณ
    return this.#celsius * 9/5 + 32;
  }
}

const t = new Temperature();
t.celsius = 25;          // เรียก setter (เหมือน assign property)
t.celsius;               // 25 — เรียก getter
t.fahrenheit;            // 77 — getter ที่คำนวณให้

Part 6: Inheritance — การสืบทอด

class สามารถ "สืบทอด" จาก class อื่น — เอา property/method มาใช้ต่อ แล้วเพิ่ม/แก้ได้

6.1 extends และ super

javascript
class Animal {
  constructor(name) {
    this.name = name;
  }
  eat() {
    return this.name + " กำลังกิน";
  }
}

class Dog extends Animal {           // Dog สืบทอดจาก Animal
  constructor(name, breed) {
    super(name);                      // เรียก constructor ของ Animal
    this.breed = breed;
  }
  bark() {                            // method เพิ่มของ Dog เอง
    return this.name + " เห่า: โฮ่ง!";
  }
}

const d = new Dog("เร็กซ์", "ลาบราดอร์");
d.eat();           // "เร็กซ์ กำลังกิน"  — สืบทอดมาจาก Animal
d.bark();          // "เร็กซ์ เห่า: โฮ่ง!" — ของ Dog เอง

อธิบาย:

  • class Dog extends Animal — Dog สืบทอดทุกอย่างจาก Animal
  • super(name) — เรียก constructor ของ class แม่ (Animal) — ต้องเรียกก่อนใช้ this

6.2 เขียนทับ (Override) — เขียนทับ method ของแม่

class ลูกสามารถ "เขียนทับ" (override) method ที่สืบทอดมาจากแม่ เพื่อเปลี่ยนพฤติกรรมให้เหมาะกับตัวเอง — และยังเรียก method เดิมของแม่ได้ด้วย super.methodName():

javascript
class Cat extends Animal {
  eat() {                            // เขียนทับ eat ของ Animal
    return this.name + " กินอย่างสง่างาม";
  }
}

const c = new Cat("มีโอ");
c.eat();           // "มีโอ กินอย่างสง่างาม" — ใช้ของ Cat ไม่ใช่ของ Animal

เรียก method เดิมของแม่ด้วย super:

javascript
class Cat extends Animal {
  eat() {
    return super.eat() + " (แบบแมว)";    // เรียก eat ของ Animal แล้วเพิ่ม
  }
}

6.3 ⚠️ Composition over Inheritance (การประกอบดีกว่าการสืบทอด)

📌 คำแปลย่อ: composition over inheritance = "การประกอบดีกว่าการสืบทอด" — ใช้การจับกลุ่มความสามารถมาประกอบกัน แทนการสืบทอดเป็นชั้น ๆ

inheritance ดูทรงพลัง — แต่มันมีกับดัก: ถ้าสืบทอดลึกหลายชั้น (A → B → C → D) โค้ดจะซับซ้อน เปราะ แก้ยาก

คำแนะนำสมัยใหม่: "composition over inheritance" — แทนที่จะสืบทอดเป็นชั้น ๆ เราแค่ "ประกอบ" ความสามารถเข้าด้วยกัน เหมือนต่อ Lego — เลือกหยิบแต่ละชิ้นที่ต้องการมาประกอบ แทนที่จะสร้างสายการสืบทอดที่ยาว

⚠️ ข้อควรระวัง: pattern นี้เหมาะกับ method ที่ไม่มี state ส่วนตัว — ถ้าต้องการ state แยกต่อหัวหรือ override เฉพาะ instance พิจารณาใช้ class แทน

javascript
// แทนที่จะ class Duck extends Bird extends Animal...
// ประกอบความสามารถเข้าด้วยกัน
const canFly = {
  fly() { return this.name + " บินได้"; },
};
const canSwim = {
  swim() { return this.name + " ว่ายน้ำได้"; },
};

const duck = {
  name: "เป็ดโดนัลด์",
  ...canFly,
  ...canSwim,
};

duck.fly();        // "เป็ดโดนัลด์ บินได้"
duck.swim();       // "เป็ดโดนัลด์ ว่ายน้ำได้"

💡 this ในตัวอย่างนี้คืออะไร: spread ...canFly คัดลอก method fly มาเป็น property ของ duck ตรง ๆ → เวลาเรียก duck.fly() this ภายใน fly คือ duck เอง (กฎ "ใครเรียก ใครเป็น this" จากบทที่ 05) → จึงเข้าถึง this.name ได้

→ ใช้ inheritance ได้ แต่อย่าสืบทอดลึกเกิน 1-2 ชั้น


Part 7: Map — เก็บคู่ key-value ที่ดีกว่า object บางกรณี

จำได้ไหมว่า key ของ object ที่เรียนมาตั้งแต่ Part 1 นั้น ต้องเป็น string เสมอ (หรือ symbol ซึ่งเป็น type พิเศษ) — นั่นหมายความว่าถ้าเราใส่ตัวเลข เช่น obj[0] JavaScript จะแปลง 0 เป็น string "0" ให้อัตโนมัติ ทำให้ key เป็น object ไม่ได้เลย

Map คือโครงสร้างข้อมูลที่เก็บ key-value เหมือน object แต่แก้ข้อจำกัดนี้ได้:

Map คือโครงสร้างข้อมูลที่เก็บ key-value เหมือน object แต่ดีกว่าในบางด้าน:

javascript
const map = new Map();

map.set("name", "Alice");          // เพิ่ม
map.set("age", 25);
map.get("name");                    // "Alice" — อ่าน
map.has("age");                     // true — เช็คว่ามีไหม
map.delete("age");                  // ลบ
map.size;                            // 1 — จำนวน

// key เป็นอะไรก็ได้ — แม้แต่ object! (object ทำไม่ได้)
const userKey = { id: 1 };
map.set(userKey, "ข้อมูลของ user 1");

วน Map:

javascript
for (const [key, value] of map) {
  console.log(key, value);
}

ข้อดีของ Map เหนือ object:

  • key เป็นชนิดอะไรก็ได้ (object, function ก็เป็น key ได้)
  • มี .size (object ต้อง Object.keys(obj).length)
  • รักษาลำดับการเพิ่ม

Map และ object มีประสิทธิภาพใกล้เคียงกันในงานทั่วไป — เลือก Map เพราะความหมายของโค้ด (semantic) เหมาะกว่า เช่น key ไม่ใช่ string หรือต้องการ .size/รักษาลำดับ ไม่ใช่เพราะเรื่องความเร็ว (performance)

→ ใช้ Map เมื่อ: key ไม่ใช่ string, หรือ เพิ่ม/ลบ key บ่อย / ใช้ object เมื่อ: โครงสร้างข้อมูลที่ key รู้แน่นอน


Part 8: Set — เก็บค่าที่ไม่ซ้ำ

Set เก็บค่า — แต่ "ไม่ซ้ำกัน" (ค่าที่ซ้ำจะถูกตัดออก):

javascript
const set = new Set();

set.add(1);
set.add(2);
set.add(2);              // ซ้ำ — ไม่ถูกเพิ่ม
set.add(3);

console.log(set.size);   // 3 (ไม่ใช่ 4)
set.has(2);              // true
set.delete(1);

ประโยชน์ที่ใช้บ่อยสุด — ตัดค่าซ้ำออกจาก array:

javascript
const numbers = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(numbers)];
console.log(unique);     // [1, 2, 3, 4]

อธิบาย: ใส่ array เข้า Set → ค่าซ้ำถูกตัด → ใช้ spread ... แปลงกลับเป็น array


Part 9: JSON — แปลง object เป็นข้อความ

JSON (JavaScript Object Notation) = รูปแบบข้อความสำหรับเก็บ/ส่งข้อมูล — ใช้ส่งข้อมูลระหว่าง server (เซิร์ฟเวอร์ฝั่งหลังบ้าน) กับ client (ฝั่งผู้ใช้ เช่น browser หรือ mobile app)

9.1 object → JSON string

JSON.stringify() แปลง object ของ JS เป็นข้อความ JSON เพื่อส่งหรือเก็บ — ใส่ argument เพิ่มเพื่อจัดย่อหน้าให้อ่านง่ายได้ (JSON.stringify(obj, null, 2)):

javascript
const user = { name: "Alice", age: 25, active: true };

const json = JSON.stringify(user);
console.log(json);       // '{"name":"Alice","age":25,"active":true}'

// แบบจัดรูปแบบสวย (ใส่ย่อหน้า)
JSON.stringify(user, null, 2);

9.2 JSON string → object

ในทางกลับกัน JSON.parse() แปลงข้อความ JSON (เช่นที่ได้จาก API) กลับเป็น object ของ JS ที่ใช้งานได้:

javascript
const json = '{"name":"Alice","age":25}';
const obj = JSON.parse(json);
console.log(obj.name);   // "Alice"

9.3 ⚠️ ข้อจำกัดของ JSON

JSON เก็บได้แค่: string, number, boolean, null, array, object — ไม่รองรับ:

  • function — หายไป
  • undefined — หายไป
  • Date — กลายเป็น string (parse กลับไม่ได้ Date)
  • Map, Set — กลายเป็น {} (object ว่าง)
javascript
JSON.stringify({ fn: () => {}, val: undefined, when: new Date() });
// '{"when":"2026-..."}' — fn และ val หายไป, when กลายเป็น string

⚠️ ระวังเป็นพิเศษ — Map/Set หายข้อมูลเงียบ ๆ: JSON.stringify(new Map([["a", 1]])) ได้ผลลัพธ์เป็น "{}" — ข้อมูลทุกตัวใน Map หายหมดโดยไม่มี error หรือคำเตือนใด ๆ นี่อันตรายกว่ากรณี undefined/function เพราะ Map/Set หน้าตาเหมือน object ปกติจนกว่าจะสังเกตดี ๆ — ถ้ามี Map/Set อยู่ใน object ที่จะ JSON.stringify ต้องแปลงเป็น array ก่อน (เช่น [...map.entries()]) ไม่งั้นข้อมูลจะหายไปเงียบ ๆ

→ ถ้าต้องการ copy object ที่ซับซ้อน — ใช้ structuredClone() ไม่ใช่ JSON


Part 10: Lab — ลงมือทำ

Lab 1: Class พื้นฐาน

ฝึกสร้าง class ที่มี constructor, getter (area, perimeter) และ method ที่แก้ state (scale) — รวมความรู้พื้นฐานเรื่อง class เข้าด้วยกัน:

javascript
class Rectangle {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  get area() {                       // getter
    return this.width * this.height;
  }

  get perimeter() {
    return 2 * (this.width + this.height);
  }

  scale(factor) {
    this.width *= factor;
    this.height *= factor;
  }
}

const rect = new Rectangle(4, 3);
console.log(rect.area);            // 12
console.log(rect.perimeter);       // 14
rect.scale(2);
console.log(rect.area);            // 48

Lab 2: Private + Inheritance

ฝึกรวม private field (#balance) กับ inheritance — class ลูก (SavingsAccount) ใช้ method ของแม่ได้แต่แตะ private ของแม่โดยตรงไม่ได้ ต้องผ่าน method สาธารณะ:

javascript
class Account {
  #balance = 0;

  deposit(amount) {
    if (amount <= 0) throw new Error("จำนวนเงินต้องมากกว่า 0");
    this.#balance += amount;
  }

  get balance() {
    return this.#balance;
  }
}

class SavingsAccount extends Account {
  #interestRate;

  constructor(rate) {
    super();
    this.#interestRate = rate;
  }

  addInterest() {
    this.deposit(this.balance * this.#interestRate);
  }
}

const savings = new SavingsAccount(0.05);
savings.deposit(1000);
savings.addInterest();
console.log(savings.balance);      // 1050

Lab 3: Set ตัดค่าซ้ำ

ฝึกใช้ Set ร่วมกับ spread เพื่อตัดค่าซ้ำออกจาก array ในบรรทัดเดียว — สำนวนที่ใช้บ่อยมากในงานจริง:

javascript
const tags = ["js", "web", "js", "node", "web", "react"];
const uniqueTags = [...new Set(tags)];
console.log(uniqueTags);           // ["js", "web", "node", "react"]

Lab 4: Map นับความถี่

javascript
const words = ["a", "b", "a", "c", "a", "b"];
const count = new Map();

for (const word of words) {
  count.set(word, (count.get(word) ?? 0) + 1);
}

for (const [word, n] of count) {
  console.log(`${word}: ${n}`);    // a: 3, b: 2, c: 1
}

Part 11: Checkpoint

  1. เข้าถึง property ด้วย . กับ [ ] ต่างกันยังไง? ใช้อันไหนเมื่อไหร่?
  2. Object.keys, Object.values, Object.entries คืนอะไร?
  3. Prototype chain ทำงานยังไง?
  4. class เป็นของจริงหรือเป็นแค่ "เปลือก" ของอะไร?
  5. constructor ทำอะไร?
  6. Private field (#) ทำอะไร?
  7. static ต่างจาก method ปกติยังไง?
  8. extends กับ super ทำงานยังไง?
  9. ทำไมแนะนำ "composition over inheritance"?
  10. Map ดีกว่า object ตรงไหน? Set ใช้ทำอะไร?

Part 12: สรุปบทนี้

  • Object = กลุ่มข้อมูลแบบ key-value — เข้าถึงด้วย . หรือ [ ]
  • Object.keys/values/entries = แปลง object เป็น array เพื่อวน
  • Prototype chain = กลไกหา property — หาตัวเอง ไม่เจอ → หา prototype ต่อ ๆ ไป
  • class = เปลือกสวยงามของ prototype — constructor, method, #private, static, getter/setter
  • Inheritance (extends, super) — สืบทอดได้ แต่อย่าลึกเกินไป — เลือก composition
  • Map = key-value ที่ key เป็นอะไรก็ได้ / Set = ค่าที่ไม่ซ้ำ
  • JSON = แปลง object ↔ string — แต่ไม่รองรับ function/undefined/Date

บทต่อไป — Array และเทคนิคจัดการข้อมูล (map/filter/reduce)


← บทที่ 05 | สารบัญ | บทที่ 07: Arrays →