Babel Coder

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

beginner

โลกของ 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 โดยเราสามารถกำหนดพฤติกรรมการส่งออกค่าได้นั่นเอง


แสดงความคิดเห็นของคุณ


No any discussions