Array Methods (map / filter / reduce)
higher-order function 3 ตัวที่ครอบคลุม 80% ของงาน array chain ต่อกันได้ immutable เปลี่ยน mindset จาก imperative เป็น declarative
ลองโจทย์: มี array [1, 2, 3, 4, 5, 6] อยากได้ผลรวมของเฉพาะ เลขคู่ คูณ 10 ทำยังไง
// แบบ for loop ปกติ
const numbers = [1, 2, 3, 4, 5, 6]
let total = 0
for (let i = 0; i < numbers.length; i++) {
if (numbers[i] % 2 === 0) {
total += numbers[i] * 10
}
}
// total = 120เขียนได้ แต่ดูยาว ต้องจัดการ index เอง มีตัวแปรที่เปลี่ยนค่าได้เยอะ JS สมัยใหม่มีวิธีที่ สั้นและชัดกว่า
const total = numbers
.filter(n => n % 2 === 0)
.map(n => n * 10)
.reduce((acc, n) => acc + n, 0)
// total = 120เร็วกว่าไหม ไม่จำเป็น แต่ อ่านชัดกว่า เพราะแต่ละ method บอกเจตนาเลย คือ filter (เลือก) แล้ว map (แปลง) แล้ว reduce (รวม) นี่คือ declarative style ที่ JS สมัยใหม่ใช้ทุกที่
ทบทวน Higher-order function
จาก lesson 5: function เป็น value ส่งเป็น argument ของ function อื่นได้ array methods ทั้ง 3 ตัวเป็น higher-order function เพราะรับ function เป็น argument
// function ที่ส่งเข้าไป เรียก "callback"
[1, 2, 3].map(n => n * 2)
// ^^^^^^^^^^ callback ที่ JS เรียกให้ทุกตัว3 method ที่ต้องรู้
[1, 2, 3, 4, 5, 6].map(n => n * 2)
transform คือรัน function กับทุกตัว ได้ array ใหม่ที่ length เท่าเดิม
[0]12[1]24[2]36[3]48[4]510[5]612[2, 4, 6, 8, 10, 12]1. map แปลงค่า
เปลี่ยนทุกตัว ตามสูตรที่กำหนด ได้ array ใหม่ที่มี length เท่าเดิม แค่ value เปลี่ยน
// ดึงเฉพาะ name จาก list user
const users = [
{ name: "Top", age: 22 },
{ name: "Mint", age: 20 }
]
const names = users.map(u => u.name)
// ["Top", "Mint"]2. filter เลือก
เก็บเฉพาะตัวที่ผ่านเงื่อนไข callback return true เท่ากับเก็บ false เท่ากับทิ้ง ได้ array ใหม่ที่ length สั้นกว่าหรือเท่าเดิม
// ดึงเฉพาะ user ที่บรรลุนิติภาวะ
const adults = users.filter(u => u.age >= 18)3. reduce สะสม
สะสมค่าทีละตัว ออกมาเป็น 1 ค่าเดียว callback รับ accumulator (ค่าสะสม) กับ current item เป็นตัวที่งงสุด แต่ยืดหยุ่นที่สุด
// รวมอายุทุกคน
const totalAge = users.reduce((acc, u) => acc + u.age, 0)
// ^ initial valueMethod chaining รูปแบบที่ใช้จริง
ทั้ง 3 method return array ใหม่ เลย chain ต่อกันได้ทันที เป็นรูปแบบที่ใช้ เกือบทุกที่ในงานจริง
Inputconst numbers = [1, 2, 3, 4, 5, 6, 7, 8][1, 2, 3, 4, 5, 6, 7, 8]Filter (เฉพาะคู่).filter(n => n % 2 === 0)Map (×10).map(n => n * 10)Reduce (รวม).reduce((acc, n) => acc + n, 0)Immutability ไม่แตะของเดิม
ทบทวน lesson 7: arr.push() แก้ของเดิม แต่ map / filter / reduce return ของใหม่ ทุกครั้ง ของเดิมไม่โดนแตะ
const original = [1, 2, 3]
const doubled = original.map(n => n * 2)
console.log(original) // [1, 2, 3] ← ไม่โดนแก้
console.log(doubled) // [2, 4, 6] ← ของใหม่เพราะเหตุนี้รูปแบบ functional จึงปลอดภัย เป็นเหตุผลที่ React และ Redux เขียนด้วย map/filter ตลอด
Imperative กับ Declarative การเปลี่ยนวิธีคิด
จุดสำคัญที่ทำให้ JS สมัยใหม่ต่างจาก JS เก่าๆ
- Imperative คือบอกทีละขั้นว่าทำยังไง เช่น for loop กับตัวแปรที่เปลี่ยนค่าได้
- Declarative คือบอกแค่จะให้ทำอะไร เช่น array methods กับ immutable
ทุก framework สมัยใหม่ (React, Vue, Svelte) ใช้ declarative style ฝึกคิดแบบนี้ตั้งแต่ตอนนี้ จะต่อยอดง่ายมาก
สรุป
mapแปลงทุกตัว ได้ array length เท่าเดิมfilterเก็บเฉพาะตัวที่ callback return truereduceสะสมเป็น 1 ค่า อย่าลืม initial value- Chain ต่อกันได้ทันที เพราะแต่ละ method return array ใหม่
- Immutable คือของเดิมไม่โดนแตะ ปลอดภัยกว่า for loop
- Declarative style บอกว่าจะให้ทำอะไร อ่านง่ายกว่า bug น้อยกว่า
ใน lesson ถัดไปจะเข้า scope กับ closure ซึ่งเป็น concept ที่ทำให้ JS แปลกที่สุด และเป็นรากฐานของ React Hooks, Redux และรูปแบบสมัยใหม่ทั้งหมด