Iterators และ Iterables ใน JavaScript
ในบทที่แล้ว เราได้เรียนรู้เกี่ยวกับ Generators ซึ่งเป็นเครื่องมือที่ช่วยให้เราสร้าง Iterators ได้อย่างง่ายดาย แต่ก่อนที่เราจะไปต่อยอด เรามาทำความเข้าใจพื้นฐานของ Iterators และ Iterables กันก่อนดีกว่าครับ ว่ามันคืออะไร ทำงานอย่างไร และมีความสำคัญอย่างไร
Iterator Protocol: กฎเกณฑ์ของ Iterator
Iterator Protocol คือข้อกำหนดที่ระบุว่า object ใดๆ ก็ตามที่ต้องการเป็น Iterator จะต้องมี method ชื่อ next() ซึ่ง method นี้จะ:
- คืนค่า object ที่มี property สองตัว:
valueและdone value: คือค่าถัดไปในลำดับ (sequence)done: เป็น boolean ที่บอกว่า iterator ได้สิ้นสุดลงแล้วหรือไม่ (trueถ้าสิ้นสุด,falseถ้ายังมีค่าให้วนซ้ำ)
ตัวอย่าง: สร้าง Iterator แบบง่ายๆ ที่วนซ้ำเลข 1 ถึง 3
const myIterator = {
data: [1, 2, 3],
index: 0,
next: function() {
if (this.index < this.data.length) {
return { value: this.data[this.index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: false }
console.log(myIterator.next()); // { value: undefined, done: true }
จากตัวอย่างข้างต้น myIterator เป็น object ที่มี method next() ซึ่งปฏิบัติตาม Iterator Protocol อย่างเคร่งครัด
Iterable Protocol: ทำให้ Object ของเราวนซ้ำได้
Iterable Protocol คือข้อกำหนดที่ระบุว่า object ใดๆ ก็ตามที่ต้องการเป็น Iterable (วนซ้ำได้) จะต้องมี method ที่มี symbol ชื่อ Symbol.iterator ซึ่ง method นี้จะต้อง:
- คืนค่า Iterator object
พูดง่ายๆ คือ Iterable เป็น object ที่สามารถ "ให้กำเนิด" Iterator ได้
ตัวอย่าง: สร้าง Iterable ที่วนซ้ำเลข 1 ถึง 3 โดยใช้ Iterator ที่เราสร้างไว้ก่อนหน้า
const myIterable = {
data: [1, 2, 3],
[Symbol.iterator]: function() {
let index = 0;
const data = this.data; // เก็บค่า data ไว้เพื่อใช้ใน iterator
return {
next: function() {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
const iterator = myIterable[Symbol.iterator]();
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
ในตัวอย่างนี้ myIterable มี method ที่มีชื่อ Symbol.iterator ซึ่งคืนค่า Iterator object ที่สามารถวนซ้ำข้อมูลใน data ได้
การใช้ for...of Loop กับ Iterables
สิ่งที่ทำให้ Iterables มีประโยชน์อย่างมากคือการที่มันสามารถใช้ร่วมกับ for...of loop ได้อย่างง่ายดาย for...of loop จะเรียกใช้ method Symbol.iterator ของ Iterable object โดยอัตโนมัติ และวนซ้ำค่าที่ได้จาก Iterator จนกว่า done จะเป็น true
ตัวอย่าง: ใช้ for...of loop กับ myIterable ที่เราสร้างไว้ก่อนหน้า
const myIterable = {
data: [1, 2, 3],
[Symbol.iterator]: function() {
let index = 0;
const data = this.data; // เก็บค่า data ไว้เพื่อใช้ใน iterator
return {
next: function() {
if (index < data.length) {
return { value: data[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (const value of myIterable) {
console.log(value); // 1, 2, 3
}
จะเห็นได้ว่า for...of loop ช่วยให้เราวนซ้ำข้อมูลใน myIterable ได้อย่างง่ายดาย โดยไม่ต้องจัดการกับ Iterator object โดยตรง
Iterables ใน JavaScript
ข่าวดีคือ JavaScript มี Iterables ที่ built-in มาให้เราใช้งานมากมาย เช่น:
- Arrays
- Strings
- Maps
- Sets
- Arguments object
- NodeLists (DOM)
- Generators (ที่เราเรียนไปในบทที่แล้ว)
นั่นหมายความว่าเราสามารถใช้ for...of loop กับ object เหล่านี้ได้โดยตรง
ตัวอย่าง: ใช้ for...of loop กับ Array และ String
const myArray = [4, 5, 6];
for (const value of myArray) {
console.log(value); // 4, 5, 6
}
const myString = "Hello";
for (const char of myString) {
console.log(char); // H, e, l, l, o
}
กรณีใช้งานจริง: Data Streaming
Iterators และ Iterables มีประโยชน์อย่างมากในการจัดการกับ data streaming หรือการประมวลผลข้อมูลทีละส่วน ยกตัวอย่างเช่น การอ่านไฟล์ขนาดใหญ่ทีละบรรทัด
ตัวอย่าง (จำลอง): สร้าง Iterable ที่จำลองการอ่านข้อมูลจากไฟล์ทีละบรรทัด
const fileReader = {
lines: [
"This is line 1.",
"This is line 2.",
"This is line 3."
],
[Symbol.iterator]: function() {
let index = 0;
const lines = this.lines;
return {
next: function() {
if (index < lines.length) {
return { value: lines[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
};
for (const line of fileReader) {
console.log(line);
}
แม้ว่าตัวอย่างนี้จะเป็นเพียงการจำลอง แต่แนวคิดนี้สามารถนำไปประยุกต์ใช้กับการอ่านไฟล์จริงๆ ได้ โดยใช้ Node.js หรือ Fetch API
ข้อผิดพลาดที่พบบ่อย
- ลืม Implement
Symbol.iterator: ถ้าคุณพยายามใช้for...ofloop กับ object ที่ไม่ได้ ImplementSymbol.iteratorคุณจะได้รับ error: "TypeError: object is not iterable" - Iterator ไม่สิ้นสุด: ถ้า Iterator ของคุณไม่เคย return
done: truefor...ofloop จะทำงานไปเรื่อยๆ ไม่มีวันจบ (infinite loop) - Scope ของ
index: ระวังเรื่อง scope ของตัวแปรindexใน Iterator ถ้าคุณไม่ได้ประกาศindexภายใน function scope ของSymbol.iteratorคุณอาจเจอปัญหา unexpected behavior ได้
สรุป
Iterators และ Iterables เป็นแนวคิดพื้นฐานที่สำคัญในการทำความเข้าใจการวนซ้ำใน JavaScript Iterators กำหนดวิธีการเข้าถึงข้อมูลทีละส่วน ส่วน Iterables กำหนดวิธีการสร้าง Iterators การใช้ for...of loop ช่วยให้เราวนซ้ำข้อมูลจาก Iterables ได้อย่างง่ายดาย และ JavaScript มี Iterables ที่ built-in มาให้เราใช้งานมากมาย
ในบทต่อไป เราจะไปดู Modules Patterns โดยเริ่มจาก Revealing Module Pattern กันครับ