[JavaScript] forEach, for-in และ for-of ใช้ต่างกันยังไง?

Nuttavut Thongjor

โลกของ JavaScript การวนลูปไม่ได้จบแค่ for หรือ while หากแต่เรายังมี forEach for-in และ for-of สามมหาเทพแห่งการวนลูปอีกด้วย เมื่อสารพัด for อุบัติขึ้น ความลำบากระดับค่าแรง 300 จึงปรากฎแก่เราชาวโปรแกรมเมอร์ว่า เอ็งจะมี for เยอะ ๆ เพื่ออะไร

forEach ท่องไว้คือวนลูปอาร์เรย์

forEach หรือชื่อสามัญทางวิทยาศาสตร์คือ Array.prototype.forEach() นั้นใช้เพื่อวนลูปรอบอาร์เรย์ โดยหลักแล้วเราจึงใช้ forEach เพื่อเข้าถึงแต่ละอีลีเมนต์ในอาร์เรย์ได้ ดังนี้

JavaScript
1const arr = ['B', 'a', 'b', 'e', 'l', 'C', 'o', 'd', 'e', 'r']
2
3arr.forEach((char) => console.log('=>', char))
4
5// ผลลัพธ์
6// => B
7// => a
8// => b
9// => e
10// => l
11// => C
12// => o
13// => d
14// => e
15// => r

forEach นั้นเป็นเมธอดที่รับฟังก์ชันเข้ามาเป็นอาร์กิวเมนต์ โดยฟังก์ชันดังกล่าวจะถูกเรียกทุกครั้งที่วนลูปไปในแต่ละอีลีเมนต์ของอาร์เรย์ ฟังก์ชันนี้จำเป็นต้องรับพารามิเตอร์หนึ่งตัวเป็นขั้นต่ำ โดยค่านั้นคืออีลีเมนต์จากแต่ละช่องของอาร์เรย์

นอกจากฟังก์ชันนี้จะรับค่าของอีลีเมนต์แต่ละรอบจากการวนลูปได้แล้ว มันยังรับพารามิเตอร์ได้อีกสองตัว คือ index ของแต่ละอีลีเมนต์ และ array ที่สื่อถึงอาร์เรย์ต้นฉบับของการวนลูป

JavaScript
1const arr = ['B', 'a', 'b', 'e', 'l']
2
3arr.forEach((element, index, array) => console.log(element, index, array))
4
5// ผลลัพธ์
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 ในอ็อบเจ็กต์ ดังนี้

JavaScript
1const person = {
2 name: 'Somchai Haha',
3 age: 99, // ใกล้ตายแล้ว
4 gender: 'male',
5}
6
7for (let key in person) {
8 console.log(key)
9}
10
11// ผลลัพธ์
12// "name"
13// "age"
14// "gender"

แน่นอนว่าเพียงแค่ได้ค่าของ key ออกมา ก็ไม่ใช่เรื่องยากที่จะนำพาไปสู่แต่ละค่าของอ็อบเจ็กต์

JavaScript
1const person = {
2 name: 'Somchai Haha',
3 age: 99, // ใกล้ตายแล้ว
4 gender: 'male',
5}
6
7for (let key in person) {
8 console.log(key, '=>', person[key])
9}
10
11// ผลลัพธ์
12// name => Somchai Haha
13// age => 99
14// gender => male

อาร์เรย์นั้นถือเป็นหนึ่งในอ็อบเจ็กต์เช่นกัน งั้นแสดงว่าเราก็ควรวนลูปรอบอาร์เรย์ได้ด้วยซิ!

JavaScript
1const arr = ['B', 'a', 'b', 'e', 'l']
2
3for (let key in arr) {
4 console.log(key)
5}
6
7// ผลลัพธ์
8// "0"
9// "1"
10// "2"
11// "3"
12// "4"

คุณพระ! ทำไมวนลูปรอบอาร์เรย์ด้วย for-in กลับได้ค่าเป็นตัวเลข หรือนี่จะคือตัวเลขของเงินทอนวัด?

อาร์เรย์นั้นเป็นอ็อบเจ็กต์ครับ มันจึงมีทั้งค่า key และ value โดยส่วนของ key สำหรับอาร์เรย์คือ index นั่นเอง ผลลัพธ์ของการแสดง key จึงได้ค่าของ index ออกมานั่นละฮะ

ทำนองเดียวกันกับคลาส อ็อบเจ็กต์ที่สร้างมาจากคลาสก็สามารถใช้ for-in ในการวนลูปเพื่อเข้าถึง property ต่าง ๆ ของมันได้

JavaScript
1class Person {
2 constructor(name, age, gender) {
3 this.name = name
4 this.age = age
5 this.gender = gender
6 }
7
8 printDetails() {
9 // this หมายถึงอ็อบเจ็กต์ somchai ที่สร้างจากคลาส Person
10 // มี key เป็น property ต่าง ๆ
11 for (let key in this) {
12 console.log(key, '=>', this[key])
13 }
14 }
15}
16
17const somchai = new Person('Somchai Haha', 99, 'male')
18somchai.printDetails()
19// ผลลัพธ์
20// name => Somchai Haha
21// age => 99
22// gender => male

กรณีของการวนลูปด้วย for-in พร็อพเพอร์ตี้ที่จะแสดงเป็นผลลัพธ์ในการวนลูปจะต้องตั้งค่า enumerable ให้เป็น true เท่านั้นจึงจะสามารถแสดงผลได้

โลกมนุษย์อายุคือสิ่งหวงห้าม ใครถามเป็นต้องตายไปข้างนึง เราจึงเลือกจะปกปิดส่วนของอายุจากอ็อบเจ็กต์ somchai ด้วยการตั้งค่า enumerabble ของ age ให้เป็น false ดังนี้

JavaScript
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)
13
14for (let key in somchai) {
15 console.log(key, '=>', somchai[key])
16}
17
18// ผลลัพธ์
19// name => Somchai Haha
20// 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 กับอาร์เรย์

JavaScript
1const arr = ['B', 'a', 'b', 'e', 'l']
2
3for (let ele of arr) {
4 console.log(ele)
5}
6
7// ผลลัพธ์
8// "B"
9// "a"
10// "b"
11// "e"
12// "l"

จากตัวอย่างข้างต้นเราพบว่าเมื่อใช้ for-of เพื่อการวนลูปรอบอาร์เรย์ แต่ละค่าของการวนลูปจะเป็นอีลีเมนต์ในช่องต่าง ๆ ของอาร์เรย์

ลำดับถัดไปเราจะทดสอบการวนลูปด้วย for-of ผ่านอ็อบเจ็กต์กันบ้าง

JavaScript
1const person = {
2 name: 'Somchai Haha',
3 age: 99,
4 gender: 'male',
5}
6
7for (let key of person) {
8 console.log(key)
9}
10
11// ผลลัพธ์
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 ได้ ดังนี้

JavaScript
1function* even(n) {
2 let index = 0
3
4 while (index <= n) {
5 yield index
6
7 index += 2
8 }
9}
10
11const evenGenerator = even(10)
12
13for (let i of evenGenerator) {
14 console.log(i)
15}
16
17// ผลลัพธ์
18// 0
19// 2
20// 4
21// 6
22// 8
23// 10

ชั่วโมงขายของ

อยากเก่ง JavaScript ใช่ไหม? ไม่อยากแค่เรียนคอร์ส Jumpstart JavaScript จิ้มพรวดเลยจ้า

สรุป

หลังจากอ่านบทความนี้แล้ว ผมเชื่อว่าเพื่อน ๆ น่าจะสามารถแยกความแตกต่างของการใช้ forEach for-in และ for-of ออกจากกันได้ นั่นคือ forEach ใช้เพื่อวนลูปรอบอาร์เรย์ ส่วน for-in ใช้วนลูปรอบ key ของอ็อบเจ็กต์ ในขณะที่ for-of นั้นทรงพระเพาเวอร์มาก ใช้เพื่อวนลูปรอบ Iterable Objects โดยเราสามารถกำหนดพฤติกรรมการส่งออกค่าได้นั่นเอง

สารบัญ

สารบัญ

  • forEach ท่องไว้คือวนลูปอาร์เรย์
  • วนลูปรอบอ็อบเจ็กต์ด้วย for-in
  • วนลูปรอบ Iterable Object ด้วย for-of
  • ชั่วโมงขายของ
  • สรุป