پروتکل‌های Iterable و Iterator: موتور پنهان حلقه‌ی for...of

تا این‌جا بارها از حلقه‌ی for...of برای پیمایش آرایه‌ها و رشته‌ها استفاده کرده‌اید، بدون آنکه لازم باشد بدانید این حلقه دقیقاً چگونه کار می‌کند. در پشت صحنه، هر ساختاری که با for...of قابل پیمایش است، از دو پروتکل مشخص در جاوااسکریپت پیروی می‌کند: Iterable و Iterator. شناخت این دو پروتکل، پایه‌ی درک ساختارهای پیشرفته‌تری مثل Generator است که در درس‌های بعدی بررسی می‌شود.

Iterable چیست؟

یک شیء زمانی Iterable نامیده می‌شود که متدی به نام Symbol.iterator داشته باشد. این متد باید یک شیء Iterator برگرداند. آرایه، رشته، Map و Set همگی به‌صورت داخلی این متد را دارند، به همین دلیل با for...of قابل پیمایش هستند؛ اما یک شیء ساده‌ی معمولی (object literal) این متد را ندارد و بنابراین Iterable نیست:

const arr = [1, 2, 3];
console.log(typeof arr[Symbol.iterator]); // "function"

const obj = { a: 1, b: 2 };
console.log(typeof obj[Symbol.iterator]); // "undefined"

for (const item of obj) {
  console.log(item);
} // خطا: obj is not iterable

Iterator چیست؟

یک Iterator شیئی است که متد next() را دارد. هر بار فراخوانی این متد، یک شیء با دو ویژگی برمی‌گرداند: value که مقدار فعلی است، و done که مشخص می‌کند پیمایش تمام شده یا نه:

ساخت و فراخوانی دستی یک Iterator
خروجی کنسول اینجا نمایش داده می‌شود...

حلقه‌ی for...of در واقع دقیقاً همین کاری را که در مثال بالا به‌صورت دستی انجام شد، به‌طور خودکار پشت صحنه اجرا می‌کند: مقدار Symbol.iterator شیء را می‌گیرد، Iterator آن را می‌سازد، و در هر تکرار next() را فراخوانی می‌کند تا زمانی که done برابر true شود.

ساخت یک شیء Iterable سفارشی

با پیاده‌سازی دستی متد Symbol.iterator، می‌توان هر شیء دلخواهی را قابل پیمایش با for...of کرد. مثال زیر یک بازه‌ی عددی سفارشی می‌سازد:

const range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;

    return {
      next() {
        if (current <= last) {
          return { value: current++, done: false };
        }
        return { value: undefined, done: true };
      },
    };
  },
};

for (const num of range) {
  console.log(num); // 1, 2, 3, 4, 5
}

در این مثال، range خودش Iterable است چون متد Symbol.iterator دارد، و مقدار برگشتی این متد، خودش یک Iterator است چون متد next را دارد. این جداسازی بین دو پروتکل، به شیء اجازه می‌دهد هم‌زمان چند پیمایش مستقل روی خودش داشته باشد، چون هر بار فراخوانی Symbol.iterator یک Iterator تازه می‌سازد.

جدول مقایسه‌ی Iterable و Iterator

ویژگی Iterable Iterator
متد الزامی Symbol.iterator next
نقش معرفی می‌کند که شیء قابل پیمایش است واقعاً پیمایش را انجام می‌دهد
مثال داخلی Array، String، Map، Set مقدار برگشتی arr[Symbol.iterator]()

نکات کلیدی

  • یک شیء زمانی Iterable است که متد Symbol.iterator داشته باشد و این متد یک Iterator برگرداند.
  • یک Iterator شیئی با متد next است که در هر فراخوانی، شیء value/done برمی‌گرداند.
  • حلقه‌ی for...of به‌طور خودکار همین فرآیند Symbol.iterator و next را پشت صحنه اجرا می‌کند.
  • اشیاء معمولی (object literal) به‌طور پیش‌فرض Iterable نیستند، مگر آنکه Symbol.iterator به‌صورت دستی به آن‌ها اضافه شود.

جمع‌بندی

پروتکل‌های Iterable و Iterator، مکانیزم پنهان پشت هر پیمایشی با for...of در جاوااسکریپت هستند. Iterable مشخص می‌کند یک شیء قابل پیمایش است، در حالی که Iterator خود فرآیند پیمایش گام‌به‌گام را با متد next انجام می‌دهد. درک این دو پروتکل، پیش‌نیاز مستقیم درس بعدی درباره‌ی Generator است، ساختاری که ساخت Iterator سفارشی را بسیار ساده‌تر می‌کند.