JavaScript
Built-in Classes
Set
ใช้ Set เก็บ collection ของค่าที่ไม่ซ้ำกัน — สร้าง เพิ่ม ลบ วนลูป และกำจัดค่าซ้ำจาก array
Set คืออะไร — collection ที่เก็บเฉพาะค่าที่ไม่ซ้ำกัน
คุณใช้ array เก็บข้อมูลมาหลายบทแล้ว — array เก็บค่าเรียงลำดับ มี index และยอมให้ค่าซ้ำได้ แต่มีอีกปัญหาหนึ่งที่เจอบ่อยคือ **"อยากรู้ว่าค่านี้เคยถูกเพิ่มไปแล้วหรือยัง"** — เช่น เก็บรายชื่อผู้ใช้ที่ไม่ซ้ำ, เก็บ tag ที่เคยใช้, หรือกำจัดค่าซ้ำออกจากข้อมูล **Set** (พิมพ์ใหญ่นำหน้า `S`) คือ built-in object ใน JavaScript ที่ออกแบบมาเพื่อเก็บ **collection ของค่าที่ไม่ซ้ำกัน** — ไม่ว่าจะเพิ่มค่าเดิมกี่ครั้ง Set จะเก็บไว้เพียงครั้งเดียว Set ต่างจาก array ตรงจุดสำคัญ: - **ไม่มี index** — ไม่สามารถเข้าถึงด้วย `set[0]` - **ไม่เก็บค่าซ้ำ** — `.add("A")` แล้ว `.add("A")` อีกครั้ง — ใน Set จะมี "A" ตัวเดียว - **ไม่เรียงลำดับตาม index** — แต่ยังจำลำดับการเพิ่ม (insertion order) สำหรับการวนลูป Set ใช้เมื่อไหร่: - อยากเก็บ collection ของค่าที่ไม่ซ้ำกัน - อยากเช็กว่ามีค่าบางอย่างอยู่แล้วหรือยัง — `.has()` เร็วกว่า `array.includes()` - อยากกำจัดค่าซ้ำออกจาก array — `[...new Set(arr)]`
// === Array — เก็บค่าซ้ำได้ ===
const arr = [];
arr.push("A");
arr.push("B");
arr.push("A");
console.log(arr); // ["A", "B", "A"] ← "A" ซ้ำ 2 ครั้ง
console.log(arr.length); // 3
// === Set — ไม่เก็บค่าซ้ำ ===
const set = new Set();
set.add("A");
set.add("B");
set.add("A"); // ← "A" มีอยู่แล้ว — ไม่ถูกเพิ่มซ้ำ
console.log(set); // Set(2) { "A", "B" } ← "A" ตัวเดียว
console.log(set.size); // 2- Set เก็บเฉพาะค่าที่ไม่ซ้ำ — `.add()` ค่าเดิมอีกครั้งไม่มีผลอะไร
- Set ไม่มี index — ไม่สามารถ `set[0]` ได้ — ใช้ `.has()` เช็ก หรือวนลูปเอา
- Set มี property `.size` — ไม่ใช่ `.length` (ต่างจาก array)
- Set จำ insertion order — วนลูปจะได้ค่าตามลำดับที่เพิ่มเข้าไป
สร้าง Set — `new Set()`, เพิ่มค่าด้วย `.add()`, เช็กด้วย `.has()`
วิธีใช้ Set เริ่มจาก 3 เมธอดหลักที่ใช้บ่อยที่สุด: - **`new Set([iterable])`** — สร้าง Set ใหม่ เลือกส่ง iterable (เช่น array) เพื่อเติมค่าเริ่มต้นได้ - **`.add(value)`** — เพิ่มค่าเข้า Set — คืนค่า `this` (ตัว Set เอง) จึง chain ได้ - **`.has(value)`** — เช็กว่าค่าใดมีอยู่ใน Set แล้วหรือยัง — คืน `true`/`false` - **`.size`** — (property, ไม่ใช่ method) บอกจำนวนสมาชิกใน Set Set รับค่าได้ทุก type — number, string, boolean, object, array, NaN, undefined, null **การ chain `.add()`**: เพราะ `.add()` คืน `this` — คุณเขียน `.add(v1).add(v2).add(v3)` ได้ — วิธีนี้เจอบ่อยในโค้ดที่ต้องการเติมค่าเริ่มต้นหลายตัว
// === สร้าง Set ว่าง แล้วเติมค่า ===
const fruits = new Set();
fruits.add("apple");
fruits.add("banana");
fruits.add("apple"); // ซ้ำ — ไม่มีผล
console.log(fruits.size); // 2 (apple, banana)
console.log(fruits.has("apple")); // true
console.log(fruits.has("orange")); // false
// === สร้าง Set จาก array — เติมค่าเริ่มต้น ===
const tags = new Set(["js", "css", "html"]);
console.log(tags.size); // 3
console.log(tags.has("css")); // true
// === Chain .add() — เจอบ่อยในโค้ดจริง ===
const colors = new Set()
.add("red")
.add("green")
.add("blue");
console.log(colors.size); // 3| Method / Property | หน้าที่ | คืนค่า | หมายเหตุ |
|---|---|---|---|
| `new Set()` | สร้าง Set ว่าง | `Set` | ส่ง iterable เพื่อเติมค่าเริ่มต้นได้ |
| `.add(v)` | เพิ่ม `v` เข้า Set | `this` (ตัว Set) | ถ้า `v` ซ้ำ — ไม่มีผล |
| `.has(v)` | เช็กว่า `v` มีอยู่ไหม | `boolean` | เร็วกว่า `array.includes()` |
| `.size` | จำนวนสมาชิก | `number` | property — ไม่ใช่ method — **ไม่ใช่ `.length`** |
- `.add()` คืน `this` — chain ได้: `set.add(1).add(2).add(3)`
- `.has()` ใช้เช็กก่อนเพิ่ม — ป้องกันการทำงานซ้ำซ้อนกับค่าที่มีอยู่แล้ว
- Set เก็บค่าทุก type ได้ — รวม `NaN`, `undefined`, `null`, object, array
- `.size` คือ property — อย่าเรียก `set.size()` เพราะไม่ใช่ function
การลบค่าออกจาก Set — `.delete()` และ `.clear()`
Set มี 2 วิธีในการลบค่า: - **`.delete(value)`** — ลบค่าที่ระบุออกจาก Set — คืน `true` ถ้าลบสำเร็จ (ค่ามีอยู่ใน Set), คืน `false` ถ้าค่านั้นไม่มีอยู่ - **`.clear()`** — ลบค่าทั้งหมดใน Set — ไม่คืนค่า (`undefined`) ข้อสังเกต: - `.delete()` ไม่ throw error แม้ค่าที่ลบจะไม่มีอยู่ — แค่คืน `false` - `.clear()` ทำให้ Set ว่าง — `.size` กลายเป็น `0` - `.delete()` ลบได้ทีละหนึ่งค่า — ไม่มี `.deleteAll()` — ถ้าอยากลบหลายค่าใช้ลูป หรือสร้าง Set ใหม่
const letters = new Set(["A", "B", "C", "D"]);
console.log(letters.size); // 4
// === .delete() — คืน true/false ===
console.log(letters.delete("B")); // true ← ลบสำเร็จ
console.log(letters.delete("Z")); // false ← "Z" ไม่มีอยู่
console.log(letters.size); // 3 (A, C, D)
// === ลบทั้งหมด ===
letters.clear();
console.log(letters.size); // 0
console.log(letters); // Set(0) {}
// === .delete() ใช้ condition ได้ ===
const scores = new Set([10, 20, 30, 40]);
if (scores.has(20)) {
scores.delete(20); // ลบเฉพาะเมื่อมีอยู่
}
console.log([...scores]); // [10, 30, 40]| Method | หน้าที่ | คืนค่า | ถ้าค่าไม่มีอยู่ |
|---|---|---|---|
| `.delete(v)` | ลบ `v` จาก Set | `true` / `false` | คืน `false` — ไม่ error |
| `.clear()` | ลบทุกค่าใน Set | `undefined` | Set ว่าง — `.size` = 0 |
- `.delete()` ลบทีละ 1 ค่า — ถ้าต้องลบหลายค่าให้วนลูป หรือสร้าง Set ใหม่จากค่าที่ต้องการเก็บ
- `.delete()` ไม่ throw error แม้ค่าจะไม่มีอยู่ — แค่คืน `false`
- `.clear()` ล้าง Set ทันที — ใช้เมื่อเริ่มใหม่หรือรีเซ็ตสถานะ
- เช็ก `.has()` ก่อน `.delete()` — ไม่จำเป็น แต่ช่วยให้ code อ่านง่ายขึ้นเมื่อมี logic ซับซ้อน
การวนลูปกับ Set — `for...of`, `.forEach()` และการแปลงเป็น array
Set เป็น iterable (คุณเรียนเรื่อง iterable protocol ในบทที่ 100) — จึงใช้กับ `for...of`, spread (`...`), และ desctructuring ได้ **วิธีวนลูป Set**: - **`for...of`** — ตรงไปตรงมาที่สุด: `for (const value of mySet) {}` - **`.forEach(callback)`** — คล้าย `Array.prototype.forEach` — รับ callback `(value, valueAgain, set)` — สังเกตว่า argument ตัวที่สองก็คือ value (เหมือนตัวแรก) — ออกแบบมาเพื่อให้ signature ตรงกับ `.forEach()` ของ Map - **`.values()`** — คืน iterator ของค่าทั้งหมด (แต่ default iterator ของ Set ก็คือ `.values()` — จึงไม่จำเป็นต้องเรียกแยก) - **`.keys()`** — คืน iterator ของค่าทั้งหมด (เหมือน `.values()` — มีไว้ให้ API สมมาตรกับ Map) - **`.entries()`** — คืน iterator ของ `[value, value]` — มีไว้ให้ API สมมาตรกับ Map เช่นกัน **แปลง Set → Array**: `[...mySet]` หรือ `Array.from(mySet)` — ได้ array ของค่าทั้งหมด เรียงตาม insertion order
const items = new Set(["🍎", "🍌", "🍊"]);
// === วิธีที่ 1: for...of — ตรงที่สุด ===
for (const item of items) {
console.log(item); // 🍎, 🍌, 🍊
}
// === วิธีที่ 2: .forEach() ===
items.forEach((value) => {
console.log(value); // 🍎, 🍌, 🍊
});
// === .forEach() — arguments: (value, valueAgain, set) ===
items.forEach((v1, v2, set) => {
console.log(v1, v2); // 🍎 🍎, 🍌 🍌, 🍊 🍊
// v1 === v2 — Set ไม่มี key → ส่ง value 2 ครั้งให้ตรง API Map
});
// === แปลง Set → Array ===
console.log([...items]); // ["🍎", "🍌", "🍊"]
console.log(Array.from(items)); // ["🍎", "🍌", "🍊"]
// === .values() / .keys() / .entries() ===
console.log([...items.values()]); // ["🍎", "🍌", "🍊"]
console.log([...items.keys()]); // ["🍎", "🍌", "🍊"] — เหมือน .values()
console.log([...items.entries()]); // [["🍎","🍎"],["🍌","🍌"],["🍊","🍊"]]- `.forEach()` ของ Set ส่ง `(value, valueAgain, set)` — argument ที่ 2 เป็น value ซ้ำ — ออกแบบมาให้ API เดียวกับ Map
- Set เป็น iterable — ใช้ `for...of`, spread `...`, `Array.from()` ได้ — เหมือน array
- `.values()` และ `.keys()` ของ Set คืนค่าเหมือนกัน — ทั้งคู่มีไว้เพื่อให้ API สมมาตรกับ Map
- เมื่อต้องการแปลง Set กลับเป็น array — `[...mySet]` คือวิธีที่สั้นและชัดที่สุด
Set ในงานจริง — กำจัดค่าซ้ำจาก array
หนึ่งใน use case ที่ใช้ Set บ่อยที่สุดคือ **การกำจัดค่าซ้ำจาก array** — แค่ `[...new Set(arr)]` ก็ได้ array ที่ไม่มีค่าซ้ำแล้ว **ทำไมถึงใช้ Set**: - เขียนสั้น — 1 บรรทัด - ไม่ต้องวนลูปเอง - ใช้กับ iterable ใด ๆ ได้ (string, Map keys, ฯลฯ) - `.has()` ใช้ตรวจสอบความเป็นสมาชิกได้เร็ว (O(1) โดยเฉลี่ย) **Use case อื่นที่เจอบ่อย**: - เก็บรายการ tag หรือ category ที่ไม่ซ้ำ - เช็กว่าผู้ใช้เคยคลิกปุ่มนี้แล้วหรือยัง (track interaction) - กรองข้อมูลที่ซ้ำออกก่อนส่ง API - Union / Intersection ของ 2 collection — แต่ระวังว่าต้องใช้ spread ร่วมกับ filter
// === กำจัดค่าซ้ำ — 1 บรรทัด ===
const numbers = [1, 2, 2, 3, 3, 3, 4];
const unique = [...new Set(numbers)];
console.log(unique); // [1, 2, 3, 4]
// === ใช้กับ string — กำจัดตัวอักษรซ้ำ ===
const chars = [...new Set("hello")];
console.log(chars.join("")); // "helo"
// === กรอง tag ที่ซ้ำ ===
const allTags = ["js", "css", "js", "html", "css", "js"];
const uniqueTags = [...new Set(allTags)];
console.log(uniqueTags); // ["js", "css", "html"]
// === Union — รวมค่าทั้งหมดที่ไม่ซ้ำ ===
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
const union = new Set([...a, ...b]);
console.log([...union]); // [1, 2, 3, 4]
// === Intersection — ค่าที่มีร่วมกัน ===
const intersection = new Set([...a].filter(v => b.has(v)));
console.log([...intersection]); // [2, 3]
// === Difference — ค่าที่อยู่ใน a แต่ไม่อยู่ใน b ===
const difference = new Set([...a].filter(v => !b.has(v)));
console.log([...difference]); // [1]- `[...new Set(arr)]` — กำจัดค่าซ้ำจาก array ใน 1 บรรทัด — เป็น idiom ที่เจอบ่อยที่สุดของ Set
- แปลง Set ↔ Array ได้ทั้งไปและกลับ — `new Set(arr)` และ `[...set]`
- ใช้ `.has()` ตรวจสอบสมาชิก — เร็วกว่า `array.includes()` เมื่อข้อมูลใหญ่
- Union / Intersection / Difference — ใช้ spread + filter + Set — ไม่มี method ในตัว
ข้อควรระวังกับ Set — เรื่องที่มักพลาด
Set มีพฤติกรรมบางอย่างที่ต่างจาก array และ object — ถ้าไม่ระวังอาจพลาดได้ง่าย **4 เรื่องที่มักพลาด**: 1. **Object/Array เทียบด้วย reference** — `set.add({})` แล้ว `set.has({})` ได้ `false` — เพราะคนละ reference 2. **NaN** — Set ถือว่า `NaN` เท่ากับ `NaN` (ต่างจาก `===` ที่ `NaN === NaN` เป็น `false`) — `set.add(NaN)` แล้ว `set.has(NaN)` ได้ `true` 3. **ไม่มี index** — `set[0]` ได้ `undefined` — ต้องแปลงเป็น array ก่อน: `[...set][0]` 4. **`.size` ไม่ใช่ `.length`** — `set.length` ได้ `undefined` 5. **Set เก็บค่าได้อย่างเดียว** — ไม่มี key-value pair — ถ้าอยากเก็บ key-value ใช้ Map
// === ข้อผิดพลาด 1: Object เทียบด้วย reference ===
const s = new Set();
s.add({ name: "Mai" });
console.log(s.has({ name: "Mai" })); // false ← คนละ object ใน memory!
// วิธีที่ถูก — เก็บ reference ไว้
const obj = { name: "Mai" };
s.add(obj);
console.log(s.has(obj)); // true ← reference เดียวกัน
// === ข้อผิดพลาด 2: NaN ===
const n = new Set();
n.add(NaN);
n.add(NaN); // ซ้ำ — ไม่ถูกเพิ่ม
console.log(n.size); // 1
console.log(n.has(NaN)); // true ← Set ถือว่า NaN === NaN
// === ข้อผิดพลาด 3: ไม่มี index ===
const data = new Set([10, 20, 30]);
console.log(data[0]); // undefined ← Set ไม่ใช่ array!
console.log([...data][0]); // 10 ← แปลงเป็น array ก่อน
// === ข้อผิดพลาด 4: .size ไม่ใช่ .length ===
console.log(data.size); // 3
console.log(data.length); // undefined
// === ข้อผิดพลาด 5: เก็บได้แค่ value — ไม่ใช่ key-value ===
// ❌ const map = new Set(); map.set("key", "value");
// ✅ const map = new Map(); map.set("key", "value");| ข้อผิดพลาด | สาเหตุ | วิธีแก้ |
|---|---|---|
| `.has(obj)` เป็น `false` ทั้งที่หน้าตาเหมือนกัน | object/array เทียบด้วย reference — ไม่ใช่ deep equality | เก็บ reference ตัวเดิมไว้ — หรือใช้ primitive แทน |
| คิดว่า `.add(NaN)` ซ้ำได้ | Set ถือว่า `NaN` === `NaN` — เก็บ `NaN` ได้ครั้งเดียว | ไม่ต้องกังวล — Set จัดการ NaN ให้ถูกต้องแล้ว |
| `set[0]` ได้ `undefined` | Set ไม่ใช่ array — ไม่มี index | ใช้ `[...set][0]` หรือ `Array.from(set)[0]` |
| `set.length` ได้ `undefined` | `.length` เป็นของ array — Set ใช้ `.size` | ใช้ `set.size` เสมอ |
| อยากเก็บ key-value ด้วย Set | Set เก็บได้แค่ value — ไม่มี key | ใช้ Map แทน — `new Map()` |
- object/array ใน Set เทียบด้วย reference — หน้าตาเหมือนกันไม่พอ ต้องเป็น object เดียวกัน
- NaN ใน Set — `.add(NaN)` แล้ว `.has(NaN)` ได้ true — Set handle NaN ถูกต้อง
- Set ไม่มี index — `[...set][n]` คือวิธีดึงค่าตามตำแหน่ง (แต่ O(n) — อย่าใช้ในลูป)
- `.size` ไม่ใช่ `.length` — จำให้ติด: array → length, Set → size, Map → size
- Set ไม่ใช่ key-value store — ใช้ Map เมื่อต้องการ key-value — ใช้ Set เมื่อสนใจแค่ value