JavaScript
Array Methods
filter
เรียนรู้ filter() — method กรอง element ตามเงื่อนไขและคืน array ใหม่ (ขนาด ≤ ต้นฉบับ) โดยไม่เปลี่ยนต้นฉบับ ตั้งแต่ callback return true/false, truthy/falsy auto-conversion, หลายเงื่อนไขด้วย && และ ||, filter() + map() ต่อกัน, ข้อผิดพลาดที่พบบ่อย ไปจนถึง filter() vs for loop ผ่าน Lab 3 ข้อ
`filter()` คืออะไร — กรองเฉพาะ element ที่ผ่านเงื่อนไข
`filter()` เป็น method ของ array ที่รับ callback function — เรียก callback ทุกตัวใน array และคืน (return) array ใหม่ที่ประกอบด้วยเฉพาะ element ที่ callback return ค่าเป็น `true` (หรือ truthy) กฎสำคัญ: • `filter()` จะคืน array ใหม่เสมอ — ขนาด ≤ ต้นฉบับ (อาจน้อยกว่าหรือเท่ากัน) • `filter()` ไม่เปลี่ยน array ต้นฉบับ (immutable method) • callback ต้อง return boolean (true/false) — หรือค่าใด ๆ ที่ถูกแปลงเป็น boolean ได้ • ถ้าไม่มี element ไหนผ่านเงื่อนไขเลย — `filter()` จะคืน **array ว่าง** `[]` — ไม่ error ใช้ `filter()` เมื่อคุณอยากได้เฉพาะบาง element จาก array — เช่น กรองคะแนนที่ผ่าน, สินค้าที่มีในสต็อก, หรือรายการที่ตรงตามเงื่อนไข
const nums = [1, 2, 3, 4, 5, 6];
// filter() รับ arrow function ที่ return true/false
const evens = nums.filter(n => n % 2 === 0);
console.log(evens); // [2, 4, 6] — array ใหม่
console.log(nums); // [1, 2, 3, 4, 5, 6] ← ต้นฉบับไม่เปลี่ยน ✓
สิ่งสำคัญที่ต้องรู้: • callback จะรับ element แต่ละตัว — `n` คือ element (1, 2, 3, 4, 5, 6) ตามลำดับ • callback ต้อง return เงื่อนไข — `n % 2 === 0` คืน `true` สำหรับเลขคู่ และ `false` สำหรับเลขคี่ • ถ้า return `true` → element นั้นจะถูกเก็บใน array ใหม่ — ถ้า `false` → element นั้นจะถูกข้าม • ขนาดของ array ผลลัพธ์อาจน้อยกว่าเดิม — ในตัวอย่างนี้ 6 → 3 (ลดลงครึ่งหนึ่ง)
filter() รับ element แต่ละตัว → ส่งให้ callback เช็ก true/false → เก็บเฉพาะ element ที่ได้ true → คืน array ใหม่ (ขนาด ≤ ต้นฉบับ)
Callback ของ `filter()` — return true/false และ truthy/falsy
callback function ที่ส่งให้ `filter()` รับ parameter ได้ 3 ตัว (เหมือน `map()`): • **element** — ค่าของ element ปัจจุบัน (ใช้บ่อยที่สุด) • **index** — ตำแหน่ง index ของ element ปัจจุบัน • **array** — array ต้นฉบับทั้งหมด (ใช้น้อย) จุดสำคัญของ `filter()` ที่ต่างจาก `map()`: • **callback ต้อง return boolean** — `true` เก็บ `false` ข้าม • **ค่าที่ return ถูกแปลงเป็น boolean อัตโนมัติ** (truthy/falsy) • truthy → ผ่านการกรอง (เก็บ): `true`, ตัวเลขที่ไม่ใช่ 0, string ที่ไม่ว่าง, object, array • falsy → ไม่ผ่านการกรอง (ข้าม): `false`, `0`, `""`, `null`, `undefined`, `NaN` เคล็ดลับ: ถ้า object มี property เป็น boolean อยู่แล้ว — ส่ง property นั้นเป็น callback ได้เลย — `arr.filter(obj => obj.inStock)` — เพราะ `inStock` เป็น `true`/`false` อยู่แล้ว
// === ใช้ truthy/falsy โดยตรง — object property ที่เป็น boolean
const products = [
{ name: "ปากกา", inStock: true },
{ name: "สมุด", inStock: false },
{ name: "ดินสอ", inStock: true },
{ name: "ยางลบ", inStock: false },
];
const available = products.filter(p => p.inStock);
// p.inStock เป็น true/false อยู่แล้ว — ส่งตรงได้เลย!
console.log(available);
// [{ name: 'ปากกา', inStock: true }, { name: 'ดินสอ', inStock: true }]
// === ระวัง: string ว่าง "" คือ falsy — จะถูกกรองออก
const items = ["ปากกา", "", "สมุด", "", "ดินสอ"];
const nonEmpty = items.filter(item => item);
console.log(nonEmpty); // ['ปากกา', 'สมุด', 'ดินสอ']
// "" เป็น falsy → ถูกกรองออก
const fruits = ["แอปเปิ้ล", "กล้วย", "ส้ม", "มะม่วง", "องุ่น"];
// กรองเฉพาะ element ที่ index เป็นเลขคู่ (0, 2, 4)
const evenIndexes = fruits.filter((f, i) => i % 2 === 0);
console.log(evenIndexes);
// ['แอปเปิ้ล', 'ส้ม', 'องุ่น'] — index 0, 2, 4
// กรอง 3 element แรก
const firstThree = fruits.filter((f, i) => i < 3);
console.log(firstThree);
// ['แอปเปิ้ล', 'กล้วย', 'ส้ม'] — index 0, 1, 2
หลายเงื่อนไขใน `filter()` — ใช้ `&&` และ `||`
`filter()` รับ callback ได้ 1 function — แต่เงื่อนไขภายใน callback มีได้หลายเงื่อนไข โดยใช้ logical operators (`&&`, `||`, `!`): • **`&&` (AND)** — ทุกเงื่อนไขต้องเป็นจริงทั้งหมด — element ถึงจะผ่าน • **`||` (OR)** — เงื่อนไขข้อใดข้อหนึ่งเป็นจริง — element ก็ผ่าน • **`!` (NOT)** — กลับค่า boolean — `!condition` ใช้ `( )` จัดกลุ่มเงื่อนไขให้ชัดเจนเมื่อมีหลายเงื่อนไขซับซ้อน — JavaScript จะคำนวณในวงเล็บก่อน
const scores = [45, 82, 67, 91, 55, 78, 60, 88];
// กรองคะแนนที่ >= 60 และเป็นเลขคู่
const passedAndEven = scores.filter(s => s >= 60 && s % 2 === 0);
console.log(passedAndEven); // [82, 78, 60, 88]
// 82: >=60 ✓, เลขคู่ ✓ → ผ่าน
// 67: >=60 ✓, เลขคี่ ✗ → ไม่ผ่าน
// 91: >=60 ✓, เลขคี่ ✗ → ไม่ผ่าน
// กรองคะแนนที่ >= 60 และ <= 80 — ช่วงคะแนน
const inRange = scores.filter(s => s >= 60 && s <= 80);
console.log(inRange); // [67, 78, 60]
const fruits = ["แอปเปิ้ล", "กล้วย", "ส้ม", "มะม่วง", "องุ่น"];
// กรองผลไม้ที่ขึ้นต้นด้วย "ก" หรือ "ส"
const startsWith = fruits.filter(
f => f[0] === "ก" || f[0] === "ส"
);
console.log(startsWith); // ['กล้วย', 'ส้ม']
// กล้วย: f[0] = 'ก' → true ✓
// ส้ม: f[0] = 'ส' → true ✓
// === ใช้ ! (NOT) — กรองผลไม้ที่ไม่ได้ขึ้นต้นด้วย "ส"
const notStartingWith = fruits.filter(f => f[0] !== "ส");
console.log(notStartingWith);
// ['แอปเปิ้ล', 'กล้วย', 'มะม่วง', 'องุ่น']
วิธีอ่านเงื่อนไข: • `s >= 60 && s % 2 === 0` → "คะแนน ≥ 60 **และ** เป็นเลขคู่" • `f[0] === "ก" || f[0] === "ส"` → "ขึ้นต้นด้วย ก **หรือ** ส" • ถ้าเงื่อนไขเริ่มยาว — แยกเป็นตัวแปรหรือหลายบรรทัดเพื่อให้อ่านง่ายขึ้น
`filter()` + `map()` ต่อกัน — กรองแล้วแปลง
ในงานจริง `filter()` และ `map()` มักใช้ต่อกัน: • **filter ก่อน** — เพื่อลดจำนวน element เหลือเฉพาะที่ต้องการ • **map ทีหลัง** — เพื่อแปลง element ที่เหลือเป็นรูปแบบที่ต้องการ ทำไมต้อง filter ก่อน map? • filter ลดจำนวน element → map ทำงานน้อยลง → โค้ดเร็วขึ้น • ถ้า map ก่อน → ต้องแปลง element ทั้งหมด (แม้แต่ตัวที่ไม่ต้องการ) → เปลือง วิธีเขียน: `arr.filter(...).map(...)` — ต่อกันด้วย dot (.) — ผลลัพธ์ของ filter คือ array ใหม่ → map ทำงานบน array นั้น
const students = [
{ name: "สมชาย", score: 85 },
{ name: "สมหญิง", score: 62 },
{ name: "สมศรี", score: 91 },
{ name: "สมบัติ", score: 55 },
{ name: "สมปอง", score: 78 },
];
// กรองเฉพาะ score >= 80 แล้ว map เอาแค่ name
const topStudents = students
.filter(s => s.score >= 80)
.map(s => s.name);
console.log(topStudents); // ['สมชาย', 'สมศรี']
// filter เหลือ 2 คน → map แปลงเป็นชื่อ
// === เปรียบเทียบ: ถ้า map ก่อน filter หลัง — เปลือง
const wrong = students
.map(s => s.name) // แปลง 5 คน — เปลือง!
.filter(name => name.length > 5); // กรองที่หลัง
console.log(wrong); // ['สมชาย', 'สมหญิง']
// map ทำงาน 5 ครั้ง — แต่ filter หลัง map แบบนี้ก็ผิด logic
// เพราะ filter แบบนี้กรองความยาวชื่อ ไม่ใช่คะแนน!
const products = [
{ name: "ปากกา", price: 25, inStock: true },
{ name: "สมุด", price: 45, inStock: true },
{ name: "ดินสอ", price: 10, inStock: false },
{ name: "ยางลบ", price: 15, inStock: true },
{ name: "ไม้บรรทัด", price: 30, inStock: false },
];
// โจทย์: หาชื่อสินค้าที่ inStock และราคา <= 30 — เรียงเลย
const cheapAndAvailable = products
.filter(p => p.inStock && p.price <= 30)
.map(p => p.name);
console.log(cheapAndAvailable); // ['ปากกา', 'ยางลบ']
// filter: inStock=true AND price<=30 — ผ่าน 2 ชิ้น
// map: แปลงเป็นชื่อ
ข้อผิดพลาดที่พบบ่อยเมื่อใช้ `filter()`
- ใช้ `=` แทน `===` หรือ `==` ใน callback — `s.filter(p => p.inStock = true)` — คือการกำหนดค่า ไม่ใช่การเปรียบเทียบ — ผลลัพธ์ผิด — ต้องใช้ `===` หรือ `==` เท่านั้น
- คิดว่า `filter()` เปลี่ยน array ต้นฉบับ — `filter()` ไม่เปลี่ยนต้นฉบับ — เหมือน `map()` — ต้องเก็บผลลัพธ์ในตัวแปรใหม่: `const result = arr.filter(...)`
- คิดว่า `filter()` คืน element ตัวเดียว — `filter()` คืน array เสมอ — แม้จะมีแค่ 1 element ที่ผ่าน → ก็ยังคืน `[element]` — ถ้าต้องการ element ตัวเดียวใช้ `filter(...)[0]`
- ลืมว่า callback ต้อง return เงื่อนไข — `arr.filter(n => { n > 3 })` — ไม่มี return → ได้ array ว่าง — เพราะ arrow function แบบ `{ }` ต้องมี return ชัดเจน — แก้โดยใช้ shorthand `n => n > 3` หรือเติม `return`
- ตกใจเมื่อ `filter()` คืน array ว่าง — `filter()` คืน `[]` เมื่อไม่มี element ไหนผ่าน — ไม่ใช่ error — นี่คือพฤติกรรมปกติ — ตรวจสอบด้วย `result.length === 0`
const nums = [1, 2, 3, 4, 5];
// ❌ ผิด — ใช้ = แทน === (เป็นการ assign ไม่ใช่เปรียบเทียบ)
const wrong1 = nums.filter(n => n = 3);
console.log(wrong1); // [1, 2, 3, 4, 5] ← ผ่านหมด! เพราะ n = 3 assign ได้ 3 (truthy)
// ✅ ถูก — ใช้ === ในการเปรียบเทียบ
const correct1 = nums.filter(n => n === 3);
console.log(correct1); // [3]
// ❌ ผิด — ลืม return ใน callback แบบ { }
const wrong2 = nums.filter(n => {
n > 3; // ← ไม่มี return!
});
console.log(wrong2); // [] ← array ว่าง
// ✅ ถูก — ใช้ shorthand (ไม่มี { })
const correct2 = nums.filter(n => n > 3);
console.log(correct2); // [4, 5]
// ✅ ถูก — ใช้ return ชัดเจน
const correct3 = nums.filter(n => {
return n > 3;
});
console.log(correct3); // [4, 5]
const scores = [45, 82, 67, 91, 55];
// filter คืน array — แม้จะมีแค่ 1 element ที่ผ่าน
const onlyNine = scores.filter(s => s === 91);
console.log(onlyNine); // [91] ← เป็น array ไม่ใช่ 91!
console.log(onlyNine[0]); // 91 ← เข้าถึง element ตัวแรก
// ถ้าไม่มี element ไหนผ่าน → ได้ array ว่าง
const noMatch = scores.filter(s => s === 999);
console.log(noMatch); // [] ← array ว่าง
console.log(noMatch.length); // 0
// ❌ อย่าทำ — ใช้ filter เพื่อหา element ตัวแรก
const first = scores.filter(s => s > 80)[0];
console.log(first); // 82 — ได้ผล แต่ไม่ใช่วิธีที่ตรงที่สุด
`filter()` vs `for` loop — เลือกใช้ให้เหมาะ
ทั้ง `filter()` และ `for` loop ใช้กรอง array ได้ — แต่แนวคิดต่างกัน: • `filter()` — บอก **เงื่อนไข** ที่อยากได้ (declarative — "ฉันอยากได้เฉพาะ element ที่ > 50") • `for` loop — บอก **ขั้นตอน** ทีละขั้น (imperative — "เริ่มที่ index 0, ถ้า element > 50 ก็ push...") `filter()` สั้นกว่า อ่านง่ายกว่า — คนอ่านเห็นเงื่อนไขทันทีโดยไม่ต้องไล่ code ทีละบรรทัด
| เรื่อง | filter() | for loop |
|---|---|---|
| จำนวนบรรทัด | 1–2 บรรทัด | 4–6 บรรทัด |
| ต้องจัดการ index | ไม่ต้อง | ต้องจัดการเอง |
| ต้องสร้าง array เปล่า | ไม่ต้อง — filter สร้างให้ | ต้อง `const result = []` + push |
| อ่านโค้ดรู้เงื่อนไขไหม | อ่านบรรทัดเดียวรู้ทันที | ต้องไล่อ่าน logic ในลูป |
| เมื่อไหร่ควรใช้ | เมื่อต้องการกรองด้วยเงื่อนไข | เมื่อต้องการ logic ซับซ้อนมาก เช่น skip ตาม pattern ที่ไม่ใช่แค่ condition |
const scores = [45, 82, 67, 91, 55, 78];
// === ใช้ filter() — 1 บรรทัด — อ่านรู้เงื่อนไขทันที
const passed1 = scores.filter(s => s >= 60);
// === ใช้ for loop — 5 บรรทัด — ต้องไล่อ่าน
const passed2 = [];
for (let i = 0; i < scores.length; i++) {
if (scores[i] >= 60) {
passed2.push(scores[i]);
}
}
console.log(passed1); // [82, 67, 91, 78]
console.log(passed2); // [82, 67, 91, 78]
เคล็ดลับ: ถ้าโจทย์คือ "กรองตามเงื่อนไข" → ใช้ `filter()` — ถ้าโจทย์คือ "วน loop แบบมี logic ซับซ้อนหลายขั้นตอน" และผลลัพธ์ต้องใช้ `if` หลายระดับ → อาจใช้ `for` loop — แต่ส่วนใหญ่ `filter()` ก็เพียงพอ