[JavaScript] forEach, for-in และ for-of ใช้ต่างกันยังไง?
โลกของ JavaScript การวนลูปไม่ได้จบแค่ for หรือ while หากแต่เรายังมี forEach for-in และ for-of สามมหาเทพแห่งการวนลูปอีกด้วย เมื่อสารพัด for อุบัติขึ้น ความลำบากระดับค่าแรง 300 จึงปรากฎแก่เราชาวโปรแกรมเมอร์ว่า เอ็งจะมี for เยอะ ๆ เพื่ออะไร
forEach ท่องไว้คือวนลูปอาร์เรย์
forEach หรือชื่อสามัญทางวิทยาศาสตร์คือ Array.prototype.forEach()
นั้นใช้เพื่อวนลูปรอบอาร์เรย์ โดยหลักแล้วเราจึงใช้ forEach เพื่อเข้าถึงแต่ละอีลีเมนต์ในอาร์เรย์ได้ ดังนี้
1const arr = ['B', 'a', 'b', 'e', 'l', 'C', 'o', 'd', 'e', 'r']23arr.forEach((char) => console.log('=>', char))45// ผลลัพธ์6// => B7// => a8// => b9// => e10// => l11// => C12// => o13// => d14// => e15// => r
forEach นั้นเป็นเมธอดที่รับฟังก์ชันเข้ามาเป็นอาร์กิวเมนต์ โดยฟังก์ชันดังกล่าวจะถูกเรียกทุกครั้งที่วนลูปไปในแต่ละอีลีเมนต์ของอาร์เรย์ ฟังก์ชันนี้จำเป็นต้องรับพารามิเตอร์หนึ่งตัวเป็นขั้นต่ำ โดยค่านั้นคืออีลีเมนต์จากแต่ละช่องของอาร์เรย์
นอกจากฟังก์ชันนี้จะรับค่าของอีลีเมนต์แต่ละรอบจากการวนลูปได้แล้ว มันยังรับพารามิเตอร์ได้อีกสองตัว คือ index ของแต่ละอีลีเมนต์ และ array ที่สื่อถึงอาร์เรย์ต้นฉบับของการวนลูป
1const arr = ['B', 'a', 'b', 'e', 'l']23arr.forEach((element, index, array) => console.log(element, index, array))45// ผลลัพธ์6// "B" 0 ["B", "a", "b", "e", "l"]7// "a" 1 ["B", "a", "b", "e", "l"]8// "b" 2 ["B", "a", "b", "e", "l"]9// "e" 3 ["B", "a", "b", "e", "l"]10// "l" 4 ["B", "a", "b", "e", "l"]
forEach นั้นเป็นเมธอดของอาร์เรย์จึงใช้ได้กับอาร์เรย์เท่านั้น ทว่าโลกของ JavaScript นั้นขับเคลื่อนด้วยอ็อบเจ็กต์ อย่าเอาใจแต่อาร์เรย์ซิ เราควรมีวิธีวนลูปรอบอ็อบเจ็กต์เช่นกันรึเปล่า?
วนลูปรอบอ็อบเจ็กต์ด้วย for-in
รัฐประหารอยู่คู่สังคมไทยฉันใด อ็อบเจ็กต์ก็อยู่คู่ JavaScript ฉันนั้น! เพราะใด ๆ ในโลกล้วนอ็อบเจ็กต์ JavaScript จึงต้องมี for-in เพื่อก่อการวนลูปรอบอ็อบเจ็กต์นั่นเอง
ทว่าการวนลูปด้วย for-in นี้ค่าที่ได้ออกมาแต่ละตัวจะเป็นค่าของแต่ละ key ในอ็อบเจ็กต์ ดังนี้
1const person = {2 name: 'Somchai Haha',3 age: 99, // ใกล้ตายแล้ว4 gender: 'male',5}67for (let key in person) {8 console.log(key)9}1011// ผลลัพธ์12// "name"13// "age"14// "gender"
แน่นอนว่าเพียงแค่ได้ค่าของ key ออกมา ก็ไม่ใช่เรื่องยากที่จะนำพาไปสู่แต่ละค่าของอ็อบเจ็กต์
1const person = {2 name: 'Somchai Haha',3 age: 99, // ใกล้ตายแล้ว4 gender: 'male',5}67for (let key in person) {8 console.log(key, '=>', person[key])9}1011// ผลลัพธ์12// name => Somchai Haha13// age => 9914// gender => male
อาร์เรย์นั้นถือเป็นหนึ่งในอ็อบเจ็กต์เช่นกัน งั้นแสดงว่าเราก็ควรวนลูปรอบอาร์เรย์ได้ด้วยซิ!
1const arr = ['B', 'a', 'b', 'e', 'l']23for (let key in arr) {4 console.log(key)5}67// ผลลัพธ์8// "0"9// "1"10// "2"11// "3"12// "4"
คุณพระ! ทำไมวนลูปรอบอาร์เรย์ด้วย for-in กลับได้ค่าเป็นตัวเลข หรือนี่จะคือตัวเลขของเงินทอนวัด?
อาร์เรย์นั้นเป็นอ็อบเจ็กต์ครับ มันจึงมีทั้งค่า key และ value โดยส่วนของ key สำหรับอาร์เรย์คือ index นั่นเอง ผลลัพธ์ของการแสดง key จึงได้ค่าของ index ออกมานั่นละฮะ
ทำนองเดียวกันกับคลาส อ็อบเจ็กต์ที่สร้างมาจากคลาสก็สามารถใช้ for-in ในการวนลูปเพื่อเข้าถึง property ต่าง ๆ ของมันได้
1class Person {2 constructor(name, age, gender) {3 this.name = name4 this.age = age5 this.gender = gender6 }78 printDetails() {9 // this หมายถึงอ็อบเจ็กต์ somchai ที่สร้างจากคลาส Person10 // มี key เป็น property ต่าง ๆ11 for (let key in this) {12 console.log(key, '=>', this[key])13 }14 }15}1617const somchai = new Person('Somchai Haha', 99, 'male')18somchai.printDetails()19// ผลลัพธ์20// name => Somchai Haha21// age => 9922// gender => male
กรณีของการวนลูปด้วย for-in พร็อพเพอร์ตี้ที่จะแสดงเป็นผลลัพธ์ในการวนลูปจะต้องตั้งค่า enumerable ให้เป็น true เท่านั้นจึงจะสามารถแสดงผลได้
โลกมนุษย์อายุคือสิ่งหวงห้าม ใครถามเป็นต้องตายไปข้างนึง เราจึงเลือกจะปกปิดส่วนของอายุจากอ็อบเจ็กต์ somchai ด้วยการตั้งค่า enumerabble ของ age ให้เป็น false ดังนี้
1const somchai = Object.defineProperties(2 {3 name: 'Somchai Haha',4 gender: 'male',5 },6 {7 age: {8 value: 99,9 enumerable: false,10 },11 }12)1314for (let key in somchai) {15 console.log(key, '=>', somchai[key])16}1718// ผลลัพธ์19// name => Somchai Haha20// 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 กับอาร์เรย์
1const arr = ['B', 'a', 'b', 'e', 'l']23for (let ele of arr) {4 console.log(ele)5}67// ผลลัพธ์8// "B"9// "a"10// "b"11// "e"12// "l"
จากตัวอย่างข้างต้นเราพบว่าเมื่อใช้ for-of เพื่อการวนลูปรอบอาร์เรย์ แต่ละค่าของการวนลูปจะเป็นอีลีเมนต์ในช่องต่าง ๆ ของอาร์เรย์
ลำดับถัดไปเราจะทดสอบการวนลูปด้วย for-of ผ่านอ็อบเจ็กต์กันบ้าง
1const person = {2 name: 'Somchai Haha',3 age: 99,4 gender: 'male',5}67for (let key of person) {8 console.log(key)9}1011// ผลลัพธ์12// 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 ได้ ดังนี้
1function* even(n) {2 let index = 034 while (index <= n) {5 yield index67 index += 28 }9}1011const evenGenerator = even(10)1213for (let i of evenGenerator) {14 console.log(i)15}1617// ผลลัพธ์18// 019// 220// 421// 622// 823// 10
ชั่วโมงขายของ
อยากเก่ง JavaScript ใช่ไหม? ไม่อยากแค่เรียนคอร์ส Jumpstart JavaScript จิ้มพรวดเลยจ้า
สรุป
หลังจากอ่านบทความนี้แล้ว ผมเชื่อว่าเพื่อน ๆ น่าจะสามารถแยกความแตกต่างของการใช้ forEach for-in และ for-of ออกจากกันได้ นั่นคือ forEach ใช้เพื่อวนลูปรอบอาร์เรย์ ส่วน for-in ใช้วนลูปรอบ key ของอ็อบเจ็กต์ ในขณะที่ for-of นั้นทรงพระเพาเวอร์มาก ใช้เพื่อวนลูปรอบ Iterable Objects โดยเราสามารถกำหนดพฤติกรรมการส่งออกค่าได้นั่นเอง
สารบัญ
- forEach ท่องไว้คือวนลูปอาร์เรย์
- วนลูปรอบอ็อบเจ็กต์ด้วย for-in
- วนลูปรอบ Iterable Object ด้วย for-of
- ชั่วโมงขายของ
- สรุป