JavaScript
Arrays
slice
เรียนรู้ slice() — method ดึงบางส่วนของ array โดยไม่เปลี่ยนต้นฉบับ ตั้งแต่ slice(start, end) ที่ start รวม end ไม่รวม, slice(start) ดึงถึงจบ, index ติดลบนับจากท้าย, slice() เปล่า copy array, การใช้ slice() ในงานจริง (Top-N, ตัดหัวท้าย, pagination), ไปจนถึงข้อแตกต่างกับ splice() ผ่าน Lab 3 ข้อ
`slice()` คืออะไร — ตัดบางส่วนของ array โดยไม่เปลี่ยนต้นฉบับ
slice() เป็น method ใน array ใช้ดึง (extract) element บางส่วนออกมาเป็น **array ใหม่** — โดยไม่เปลี่ยน array ต้นฉบับเลย สิ่งที่ต้องรู้: • slice() คืนค่า (return) เป็น **array ใหม่** ที่มี element ที่ถูกดึงออกมา — array เก่าไม่เปลี่ยน • slice() รับ argument ได้ 2 ตัว: `start` (index เริ่มต้น) และ `end` (index สิ้นสุด) • `start` เป็น index แรกที่รวม — **รวม** element ที่ตำแหน่ง start • `end` เป็น index แรกที่ไม่รวม — **ไม่รวม** element ที่ตำแหน่ง end • slice() เป็น non-mutating method — ต่างจาก reverse(), sort(), push(), pop(), shift(), unshift() วิธีจำ: slice() = "เฉือนบางส่วนออกมาใส่จานใหม่" — ของเดิมไม่หายไปไหน
const fruits = ["แอปเปิ้ล", "กล้วย", "ส้ม", "มะม่วง", "องุ่น"];
// slice(1, 3) → เริ่มที่ index 1 (รวม "กล้วย") ถึง index 3 (ไม่รวม "มะม่วง")
const sliced = fruits.slice(1, 3);
console.log(sliced); // ["กล้วย", "ส้ม"]
console.log(fruits); // ["แอปเปิ้ล", "กล้วย", "ส้ม", "มะม่วง", "องุ่น"] — ต้นฉบับไม่เปลี่ยน ✓| เรื่อง | slice() | reverse() / sort() |
|---|---|---|
| เปลี่ยนต้นฉบับ? | ไม่ — คืน array ใหม่ | ใช่ — เปลี่ยน array ต้นฉบับโดยตรง |
| return คืออะไร | array ใหม่ที่มี element ที่ดึงออกมา | reference ของ array ต้นฉบับ (ตัวเดิม) |
| ต้อง capture return ไหม | ต้อง — `const result = arr.slice(1,3)` | ไม่ต้อง — `arr.reverse()` เปลี่ยน arr เลย |
| ใช้เมื่อไหร่ | ต้องการบางส่วนของ array โดยเก็บของเก่าไว้ | ต้องการเปลี่ยนลำดับของ array ต้นฉบับถาวร |
จุดสังเกตสำคัญ: slice() เป็น method ในกลุ่ม "accessor" (อ่านข้อมูล) — ไม่เปลี่ยนต้นฉบับ — เช่นเดียวกับ concat(), join(), includes(), indexOf() ที่เราเรียนมาแล้ว slice() กับ concat() เป็นเพื่อนกัน: ทั้งคู่สร้าง array ใหม่, ไม่ mutate ต้นฉบับ, และ return array ใหม่ให้เรา capture ไปใช้ต่อ
`start` และ `end` — index เริ่มต้นกับ index สิ้นสุด
slice(start, end) ทำงานด้วยกฎง่าย ๆ ข้อเดียว: • `start` — index แรกที่**ต้องการรวม** (inclusive) — element ที่ตำแหน่ง start จะอยู่ในผลลัพธ์ • `end` — index แรกที่**ต้องการตัดออก** (exclusive) — element ที่ตำแหน่ง end จะ**ไม่อยู่ใน**ผลลัพธ์ จำนวน element ที่ได้ = `end - start` (เมื่อ end > start และทั้งคู่ไม่เกิน length) ถ้าไม่ระบุ `end` → slice ดึงตั้งแต่ start ไปจนจบ array
const nums = [10, 20, 30, 40, 50, 60];
// index: 0 1 2 3 4 5
// === slice(2, 5) — เริ่มที่ index 2 (รวม "30") ถึง index 5 (ไม่รวม "60")
console.log(nums.slice(2, 5)); // [30, 40, 50]
// start=2 ↑ ↑ end=5 (ไม่รวม)
// ได้ 3 ตัว: 5 - 2 = 3 ✓
// === slice(0, 4) — เริ่มที่ index 0 ถึง index 4
console.log(nums.slice(0, 4)); // [10, 20, 30, 40]
// ได้ 4 ตัว: 4 - 0 = 4
// === slice(0, 3) — เอา 3 ตัวแรก
console.log(nums.slice(0, 3)); // [10, 20, 30]
// === slice(1, 1) — start === end → ได้ array เปล่า
console.log(nums.slice(1, 1)); // []
// ไม่มี element ระหว่าง index 1 ถึง 1const letters = ["a", "b", "c", "d", "e"];
// index: 0 1 2 3 4
// === slice(2) — ตั้งแต่ index 2 ถึงจบ
console.log(letters.slice(2)); // ["c", "d", "e"]
// === slice(0) — ตั้งแต่ index 0 ถึงจบ (คือทั้ง array — copy)
console.log(letters.slice(0)); // ["a", "b", "c", "d", "e"]
// === slice(3) — ตั้งแต่ index 3 ถึงจบ
console.log(letters.slice(3)); // ["d", "e"]
// === slice(10) — start > length → array เปล่า
console.log(letters.slice(10)); // []
// ไม่มี index 10 → ไม่มีอะไรให้ดึงกฎที่ควรจำ: • `end` ต้องมากกว่า `start` ถึงจะได้ element — ถ้าเท่ากันได้ array เปล่า [] • `slice(start)` ไม่มี `end` → ดึงถึง element สุดท้ายของ array • `start` เกิน length → ได้ [] — ไม่ error • `end` เกิน length → slice จะหยุดที่ element สุดท้าย — ไม่ error
`slice()` กับ index ติดลบ — นับจากท้าย array
slice() อนุญาตให้ใช้ index ติดลบ — ซึ่งนับถอยหลังจาก**ท้าย array** • `-1` = element สุดท้าย • `-2` = element รองสุดท้าย • `-3` = element ก่อนหน้านั้น • ...ไปเรื่อย ๆ วิธีคิด: index ติดลบ = `length + index` เช่น array.length = 5 → slice(-2) = slice(5 + (-2)) = slice(3) index ติดลบใช้ได้ทั้ง `start` และ `end` — ทำให้การดึง element จากท้าย array ทำได้ง่ายมาก
const nums = [10, 20, 30, 40, 50];
// index: 0 1 2 3 4
// index ติดลบ: -5 -4 -3 -2 -1
// === slice(-1) — ตัวสุดท้าย
console.log(nums.slice(-1)); // [50]
// === slice(-2) — 2 ตัวสุดท้าย (ตั้งแต่ index -2 ถึงจบ)
console.log(nums.slice(-2)); // [40, 50]
// === slice(-3, -1) — ตั้งแต่ index -3 ถึง -1 (ไม่รวม -1)
console.log(nums.slice(-3, -1)); // [30, 40]
// -3 → index 2 (30) รวม
// -1 → index 4 (50) ไม่รวม → ได้ [30, 40]
// === slice(-4, -2)
console.log(nums.slice(-4, -2)); // [20, 30]const items = ["a", "b", "c", "d", "e"];
// length = 5
// index ติดลบ: -5 -4 -3 -2 -1
// === 3 วิธีที่ให้ผลเหมือนกัน — เอา ["b", "c", "d"]
// แบบ 1: index บวกล้วน
console.log(items.slice(1, 4)); // ["b", "c", "d"]
// แบบ 2: start ติดลบ
console.log(items.slice(-4, 4)); // ["b", "c", "d"]
// -4 = 5-4 = 1, 4 = 4
// แบบ 3: ติดลบทั้งคู่
console.log(items.slice(-4, -1)); // ["b", "c", "d"]
// -4 = 1, -1 = 4
// === ดึง 2 ตัวสุดท้าย — สะดวกสุดด้วย slice(-2)
console.log(items.slice(-2)); // ["d", "e"]index ติดลบมีประโยชน์มากเมื่อ: • ต้องการ "n ตัวสุดท้าย" — `arr.slice(-n)` • ต้องการ "ทุกตัวยกเว้น n ตัวสุดท้าย" — `arr.slice(0, -n)` • ต้องการ element ก่อนตัวสุดท้าย — `arr.slice(-2, -1)` จำง่าย ๆ: มอง `-1` เป็น "ตัวสุดท้าย", `-2` เป็น "ตัวก่อนสุดท้าย" — แล้วคิดจากตรงนั้น
`slice()` แบบไม่มี argument — shallow copy ของ array ทั้งก้อน
`arr.slice()` โดยไม่ระบุ argument เลย — จะคืนค่า **shallow copy** ของ array ทั้งก้อน พฤติกรรมนี้เหมือน `arr.slice(0)` หรือ `arr.concat()` — ได้ array ใหม่ที่มี element เหมือนเดิมทุกประการ แต่เป็นคนละ reference กัน ประโยชน์ของ `arr.slice()`: • copy array ก่อน mutate — ใช้ slice() แทน concat() ก็ได้ สั้นกว่า 1 ตัวอักษร 😄 • แปลง array-like object (เช่น `arguments`, `NodeList`) เป็น array จริง — เป็น pattern ที่เจอในโค้ดเก่า • เมื่อต้องการ array ใหม่ไปใช้ต่อโดยไม่กระทบของเดิม
const original = ["a", "b", "c", "d"];
// === slice() ไม่มี argument → shallow copy
const copied = original.slice();
console.log(copied); // ["a", "b", "c", "d"]
console.log(copied === original); // false — คนละ array กัน ✓
// === แก้ไข copied — ไม่กระทบ original
copied.push("e");
copied[0] = "A";
console.log(copied); // ["A", "b", "c", "d", "e"]
console.log(original); // ["a", "b", "c", "d"] ← ไม่เปลี่ยน ✓const arr = [1, 2, 3];
// === 3 วิธี copy array — ให้ผลเหมือนกัน
const copy1 = arr.slice(); // slice() เปล่า ๆ
const copy2 = arr.concat(); // concat() เปล่า ๆ
const copy3 = arr.slice(0); // slice(0)
console.log(copy1); // [1, 2, 3]
console.log(copy2); // [1, 2, 3]
console.log(copy3); // [1, 2, 3]
// ทั้ง 3 วิธีสร้าง array ใหม่ — ต้นฉบับ arr ไม่เปลี่ยน
// slice() เป็นวิธีที่สั้นที่สุดและอ่านเข้าใจง่ายที่สุดข้อควรรู้: slice() (รวมถึง concat()) ทำเพียง **shallow copy** — ถ้า array มี object หรือ nested array อยู่ข้างใน ค่าเหล่านั้นจะถูก copy แค่ reference (ไม่ได้ deep copy) shallow copy หมายความว่า: • primitive value (string, number, boolean) — ถูก copy ค่า • object, array — ถูก copy reference (การแก้ property ใน object ของ copy จะกระทบ original) ถ้าต้องการ copy แบบไม่กระทบกันทุกชั้น — ใช้ `structuredClone()` (จะเรียนในบทถัดไป)
ใช้ `slice()` ในสถานการณ์จริง
slice() ใช้บ่อยในงานจริง — ดึงข้อมูลบางส่วน, แบ่งหน้า (pagination), copy array, หรือตัด element ที่ไม่ต้องการออก สถานการณ์ที่ใช้ slice(): • ดึง "Top N" — เอา 3 อันดับแรกของ leaderboard • ตัด header/footer — เอาเฉพาะข้อมูลแถวที่ 2 ถึงตัวรองสุดท้าย • copy ก่อนใช้ method ที่เปลี่ยนต้นฉบับ — `arr.slice().reverse()` หรือ `arr.concat().slice()` เพื่อเก็บต้นฉบับ • แบ่งหน้า (pagination) — `items.slice(startIndex, startIndex + pageSize)` • แปลง array-like เป็น array — `Array.prototype.slice.call(nodeList)` (โค้ดเก่า) หัวข้อนี้จะพาใช้ slice() ในโจทย์ที่ใกล้เคียงของจริง
// === ดึง Top 3 และ Bottom 2 ของ leaderboard
const leaderboard = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace"];
// รายชื่อเรียงตามอันดับแล้ว
// === เอา 3 คนแรก
const top3 = leaderboard.slice(0, 3);
console.log("Top 3:", top3); // ["Alice", "Bob", "Charlie"]
// === เอา 2 คนสุดท้ายด้วย index ติดลบ
const last2 = leaderboard.slice(-2);
console.log("Last 2:", last2); // ["Frank", "Grace"]
console.log("ต้นฉบับ:", leaderboard); // ไม่เปลี่ยน ✓// === ข้อมูลมี header และ footer — เอาเฉพาะ data
const rows = ["HEADER", "data1", "data2", "data3", "FOOTER"];
// ตัด header (index 0) และ footer (ตัวสุดท้าย)
const data = rows.slice(1, -1);
console.log(data); // ["data1", "data2", "data3"]
// slice(1, -1) = เริ่มที่ index 1, จบที่ index -1 (ไม่รวมตัวสุดท้าย)
// === ตัด 2 ตัวแรกและ 2 ตัวสุดท้าย
const values = [99, 100, 85, 90, 78, 88, 95, 92];
const trimmed = values.slice(2, -2);
console.log(trimmed); // [85, 90, 78, 88]// === แบ่งหน้าข้อมูล — หน้าละ 4 รายการ
const items = [
"รายการ 1", "รายการ 2", "รายการ 3", "รายการ 4",
"รายการ 5", "รายการ 6", "รายการ 7", "รายการ 8",
"รายการ 9", "รายการ 10",
];
const pageSize = 4;
// หน้า 1: index 0 ถึง 4
const page1 = items.slice(0, pageSize);
console.log("หน้า 1:", page1);
// ["รายการ 1", "รายการ 2", "รายการ 3", "รายการ 4"]
// หน้า 2: index 4 ถึง 8
const page2 = items.slice(pageSize, pageSize * 2);
console.log("หน้า 2:", page2);
// ["รายการ 5", "รายการ 6", "รายการ 7", "รายการ 8"]
// หน้า 3: index 8 ถึงจบ
const page3 = items.slice(pageSize * 2);
console.log("หน้า 3:", page3);
// ["รายการ 9", "รายการ 10"]ข้อสังเกตจากตัวอย่าง: • `slice(0, 3)` และ `slice(-2)` — ดึงด้านหน้าและด้านหลังด้วย index บวกกับ index ติดลบ — slice ทำให้ดึงช่วงข้อมูลง่าย • `slice(1, -1)` — ตัดหัวตัดท้ายด้วย index บวก + index ติดลบ — pattern ที่ใช้บ่อย • `slice(start, start + pageSize)` — ใช้ทำ pagination — slice ทำให้ logic แบ่งหน้าง่าย
จุดที่มือใหม่มักพลาด
- `end` เป็น exclusive — ไม่รวม element ที่ index end — `[1,2,3,4,5].slice(1,3)` → `[2,3]` (ไม่ใช่ `[2,3,4]`) — มือใหม่มักคาดหวังว่าทั้ง start และ end รวมหมด
- ลืม capture return — slice() ไม่ mutate ต้นฉบับ — `arr.slice(1,3)` เฉย ๆ ไม่ได้เปลี่ยน arr และไม่ได้เก็บค่า — ต้องเขียน `const result = arr.slice(1,3)`
- slice() vs splice() — slice (ไม่มี p) = ไม่เปลี่ยนต้นฉบับ, splice (มี p) = เปลี่ยนต้นฉบับ (จะเรียนบทถัดไป) — สองตัวนี้ชื่อคล้ายกันมาก แต่นิสัยตรงข้ามกัน — จำว่า slice = 'ไม่ p-lon' (ไม่เปลี่ยน)
- index ติดลบ — `slice(-1)` ได้ array ที่มีตัวสุดท้ายตัวเดียว `["last"]` — ไม่ใช่ตัวสุดท้ายเปล่า ๆ — มือใหม่บางคนคิดว่า slice(-1) จะได้ค่าเดียว ไม่ใช่อาร์เรย์
- copy เป็น shallow — `arr.slice()` copy แค่ชั้นนอก — object/nested array ข้างในยังใช้ reference เดิม — ถ้าคาดหวัง deep copy จะเจอ bug
- start >= length → ได้ [] — `[1,2].slice(5)` → `[]` — ไม่ error — มือใหม่อาจไม่ทันสังเกตว่าทำไมถึงไม่ได้ element
- start > end → ได้ [] — `[1,2,3].slice(3,1)` → `[]` — slice ไม่สลับ index ให้อัตโนมัติ
- slice() ใช้ได้กับ string ด้วย — `"hello".slice(1,3)` → `"el"` — method slice บน string พฤติกรรมคล้าย array slice มาก แต่นั่นคือ string method ไม่ใช่ array method (จะเรียนในบท string methods)