โลกของ JavaScript การวนลูปไม่ได้จบแค่ for หรือ while หากแต่เรายังมี forEach for-in และ for-of สามมหาเทพแห่งการวนลูปอีกด้วย เมื่อสารพัด for อุบัติขึ้น ความลำบากระดับค่าแรง 300 จึงปรากฎแก่เราชาวโปรแกรมเมอร์ว่า เอ็งจะมี for เยอะ ๆ เพื่ออะไร
สารบัญ
forEach ท่องไว้คือวนลูปอาร์เรย์
forEach หรือชื่อสามัญทางวิทยาศาสตร์คือ Array.prototype.forEach()
นั้นใช้เพื่อวนลูปรอบอาร์เรย์ โดยหลักแล้วเราจึงใช้ forEach เพื่อเข้าถึงแต่ละอีลีเมนต์ในอาร์เรย์ได้ ดังนี้
const arr = ['B', 'a', 'b', 'e', 'l', 'C', 'o', 'd', 'e', 'r']
arr.forEach(char => console.log('=>', char))
// ผลลัพธ์
// => B
// => a
// => b
// => e
// => l
// => C
// => o
// => d
// => e
// => r
forEach นั้นเป็นเมธอดที่รับฟังก์ชันเข้ามาเป็นอาร์กิวเมนต์ โดยฟังก์ชันดังกล่าวจะถูกเรียกทุกครั้งที่วนลูปไปในแต่ละอีลีเมนต์ของอาร์เรย์ ฟังก์ชันนี้จำเป็นต้องรับพารามิเตอร์หนึ่งตัวเป็นขั้นต่ำ โดยค่านั้นคืออีลีเมนต์จากแต่ละช่องของอาร์เรย์
นอกจากฟังก์ชันนี้จะรับค่าของอีลีเมนต์แต่ละรอบจากการวนลูปได้แล้ว มันยังรับพารามิเตอร์ได้อีกสองตัว คือ index ของแต่ละอีลีเมนต์ และ array ที่สื่อถึงอาร์เรย์ต้นฉบับของการวนลูป
const arr = ['B', 'a', 'b', 'e', 'l']
arr.forEach(
(element, index, array) =>
console.log(element, index, array)
)
// ผลลัพธ์
// "B" 0 ["B", "a", "b", "e", "l"]
// "a" 1 ["B", "a", "b", "e", "l"]
// "b" 2 ["B", "a", "b", "e", "l"]
// "e" 3 ["B", "a", "b", "e", "l"]
// "l" 4 ["B", "a", "b", "e", "l"]
forEach นั้นเป็นเมธอดของอาร์เรย์จึงใช้ได้กับอาร์เรย์เท่านั้น ทว่าโลกของ JavaScript นั้นขับเคลื่อนด้วยอ็อบเจ็กต์ อย่าเอาใจแต่อาร์เรย์ซิ เราควรมีวิธีวนลูปรอบอ็อบเจ็กต์เช่นกันรึเปล่า?
วนลูปรอบอ็อบเจ็กต์ด้วย for-in
รัฐประหารอยู่คู่สังคมไทยฉันใด อ็อบเจ็กต์ก็อยู่คู่ JavaScript ฉันนั้น! เพราะใด ๆ ในโลกล้วนอ็อบเจ็กต์ JavaScript จึงต้องมี for-in เพื่อก่อการวนลูปรอบอ็อบเจ็กต์นั่นเอง
ทว่าการวนลูปด้วย for-in นี้ค่าที่ได้ออกมาแต่ละตัวจะเป็นค่าของแต่ละ key ในอ็อบเจ็กต์ ดังนี้
const person = {
name: 'Somchai Haha',
age: 99, // ใกล้ตายแล้ว
gender: 'male'
}
for(let key in person) {
console.log(key)
}
// ผลลัพธ์
// "name"
// "age"
// "gender"
แน่นอนว่าเพียงแค่ได้ค่าของ key ออกมา ก็ไม่ใช่เรื่องยากที่จะนำพาไปสู่แต่ละค่าของอ็อบเจ็กต์
const person = {
name: 'Somchai Haha',
age: 99, // ใกล้ตายแล้ว
gender: 'male'
}
for(let key in person) {
console.log(key, '=>', person[key])
}
// ผลลัพธ์
// name => Somchai Haha
// age => 99
// gender => male
อาร์เรย์นั้นถือเป็นหนึ่งในอ็อบเจ็กต์เช่นกัน งั้นแสดงว่าเราก็ควรวนลูปรอบอาร์เรย์ได้ด้วยซิ!
const arr = ['B', 'a', 'b', 'e', 'l']
for(let key in arr) {
console.log(key)
}
// ผลลัพธ์
// "0"
// "1"
// "2"
// "3"
// "4"
คุณพระ! ทำไมวนลูปรอบอาร์เรย์ด้วย for-in กลับได้ค่าเป็นตัวเลข หรือนี่จะคือตัวเลขของเงินทอนวัด?
อาร์เรย์นั้นเป็นอ็อบเจ็กต์ครับ มันจึงมีทั้งค่า key และ value โดยส่วนของ key สำหรับอาร์เรย์คือ index นั่นเอง ผลลัพธ์ของการแสดง key จึงได้ค่าของ index ออกมานั่นละฮะ
ทำนองเดียวกันกับคลาส อ็อบเจ็กต์ที่สร้างมาจากคลาสก็สามารถใช้ for-in ในการวนลูปเพื่อเข้าถึง property ต่าง ๆ ของมันได้
class Person {
constructor(name, age, gender) {
this.name = name
this.age = age
this.gender = gender
}
printDetails() {
// this หมายถึงอ็อบเจ็กต์ somchai ที่สร้างจากคลาส Person
// มี key เป็น property ต่าง ๆ
for(let key in this) {
console.log(key, '=>', this[key])
}
}
}
const somchai = new Person('Somchai Haha', 99, 'male')
somchai.printDetails()
// ผลลัพธ์
// name => Somchai Haha
// age => 99
// gender => male
กรณีของการวนลูปด้วย for-in พร็อพเพอร์ตี้ที่จะแสดงเป็นผลลัพธ์ในการวนลูปจะต้องตั้งค่า enumerable ให้เป็น true เท่านั้นจึงจะสามารถแสดงผลได้
โลกมนุษย์อายุคือสิ่งหวงห้าม ใครถามเป็นต้องตายไปข้างนึง เราจึงเลือกจะปกปิดส่วนของอายุจากอ็อบเจ็กต์ somchai ด้วยการตั้งค่า enumerabble ของ age ให้เป็น false ดังนี้
const somchai = Object.defineProperties({
name: 'Somchai Haha',
gender: 'male'
}, {
age: {
value: 99,
enumerable: false
}
})
for(let key in somchai) {
console.log(key, '=>', somchai[key])
}
// ผลลัพธ์
// name => Somchai Haha
// gender => male
ผลลัพธ์จากการตั้งค่า enumerable เป็น false จึงทำให้อายุไม่สามารถปรากฎได้จากการวนลูปด้วย for-in นั่นแล
การใช้งาน forEach หรือ for-in นั้นเราอาจมีตัวเลือกของการเข้าถึงอีลีเมนต์ไม่มากนัก หากเราต้องการวนลูปรอบอ็อบเจ็กต์ประเภท Iterable พร้อมกำหนดพฤติกรรมได้ด้วยตนเองจากข้างในอ็อบเจ็กต์เลย สิ่งที่เราต้องการนั้นจะเป็น for-of
วนลูปรอบ Iterable Object ด้วย for-of
ก่อนที่เราจะเข้าใจการใช้งาน for-of ได้อย่างแท้จริง เราต้องลองทดสอบการวนลูปกับสิ่งที่เราทำได้ผ่าน forEach และ for-in กันก่อน นั่นคือการทดสอบวนลูป for-of ด้วยอาร์เรย์และอ็อบเจ็กต์
เริ่มจากการทดลองใช้ for-of กับอาร์เรย์
const arr = ['B', 'a', 'b', 'e', 'l']
for(let ele of arr) {
console.log(ele)
}
// ผลลัพธ์
// "B"
// "a"
// "b"
// "e"
// "l"
จากตัวอย่างข้างต้นเราพบว่าเมื่อใช้ for-of เพื่อการวนลูปรอบอาร์เรย์ แต่ละค่าของการวนลูปจะเป็นอีลีเมนต์ในช่องต่าง ๆ ของอาร์เรย์
ลำดับถัดไปเราจะทดสอบการวนลูปด้วย for-of ผ่านอ็อบเจ็กต์กันบ้าง
const person = {
name: 'Somchai Haha',
age: 99,
gender: 'male'
}
for(let key of person) {
console.log(key)
}
// ผลลัพธ์
// error: TypeError: person[Symbol.iterator] is not a function
อ. อุบลช่วยด้วยยย ทำไมเราจึงวนลูปรอบอ็อบเจ็กต์ไม่ได้เช่นเดียวกับที่วนลูปได้ด้วย for-in
for-of นั้นใช้วนลูปรอบอ็อบเจ็กต์ประเภทที่เราเรียกว่า Iterable Objects หรืออ็อบเจ็กต์ที่ใช้วนลูปได้ นั่นคือต้องทำตาม Iteration Protocol เพราะว่าอาร์เรย์นั้นเป็น iterable object จึงไม่ต้องแปลกใจที่จะใช้คู่กับ for-of ได้
สมมติเราต้องการทำการวนลูปรอบเลขคู่ตั้งแต่ 0 ถึง 10 เราจึงทำการสร้าง generator function ชื่อ even ที่รับตัวเลขเข้ามาเพื่อบอกว่าเราจะทำการสร้างเลขคู่ไปจนถึงค่าเท่าไหร่
เมื่อเราเรียกใช้ฟังก์ชันดังกล่าวเราจะได้อ็อบเจ็กต์พิเศษที่เรียกว่า generator object มีความสามารถของ iterable object จึงสามารถใช้เพื่อการวนลูปผ่าน for-of ได้ ดังนี้
function* even(n) {
let index = 0
while(index <= n) {
yield index
index += 2
}
}
const evenGenerator = even(10)
for(let i of evenGenerator) {
console.log(i)
}
// ผลลัพธ์
// 0
// 2
// 4
// 6
// 8
// 10
ชั่วโมงขายของ
อยากเก่ง JavaScript ใช่ไหม? ไม่อยากแค่เรียนคอร์ส Jumpstart JavaScript จิ้มพรวดเลยจ้า
สรุป
หลังจากอ่านบทความนี้แล้ว ผมเชื่อว่าเพื่อน ๆ น่าจะสามารถแยกความแตกต่างของการใช้ forEach for-in และ for-of ออกจากกันได้ นั่นคือ forEach ใช้เพื่อวนลูปรอบอาร์เรย์ ส่วน for-in ใช้วนลูปรอบ key ของอ็อบเจ็กต์ ในขณะที่ for-of นั้นทรงพระเพาเวอร์มาก ใช้เพื่อวนลูปรอบ Iterable Objects โดยเราสามารถกำหนดพฤติกรรมการส่งออกค่าได้นั่นเอง