۱ فوریه ۲۰۲۲

حلقه‌پذیرها

شیءهای حلقه‌پذیر تعمیمی از آرایه‌ها هستند. این مفهومی است که به ما اجازه می‌دهد تا هر شیءای را در حلقه for..of قابل استفاده کنیم.

قطعا آرایه‌ها حلقه‌پذیر هستند. اما شیءهای درون‌ساخت دیگری هم هستند که حلقه‌پذیرند. برای مثال، رشته‌ها هم حلقه‌پذیرند.

اگر یک شیء به طور فنی آرایه نباشد، اما یک مجموعه (لیست یا دسته) از چیزها را نشان دهد، for..of یک سینتکس عالی برای حلقه‌زدن درون آن است، پس بیایید ببینیم چگونه چنین کاری را انجام دهیم.

ویژگی Symbol.iterator

ما می‌توانیم به راحتی مفهوم حلقه‌پذیرها را با ایجاد حلقه‌پذیر خودمان درک کنیم.

برای مثال، ما یک شیء داریم که آرایه نیست، اما برای استفاده در for..of مناسب است.

مانند یک شیء range که بازه‌ای از اعداد را نشان می‌دهد:

let range = {
  from: 1,
  to: 5
};

// :کار کند for..of می‌خواهیم که
// for(let num of range) ... num=1,2,3,4,5

برای اینکه شیء range را حلقه‌پذیر کنیم (و به این ترتیب بگذاریم for..of کار کند) ما نیاز داریم که یک متد به اسم Symbol.iterator را به شیء اضافه کنیم ( یک سمبل خاص درون ساخت که فقط برای این کار است).

  1. زمانی که for..of شروع می‌شود، متد را یک بار صدا می‌زند (یا اگر پیدا نشود ارور می‌دهد). متد باید یک حلقه‌زننده را برگرداند – شیءای که متد next را دارد.
  2. همینطور رو به جلو، for..of تنها با شیء برگردانده شده کار می‌کند.
  3. زمانی که for..of مقدار بعدی را نیاز دارد، روی آن شیء next() را صدا می‌زند.
  4. نتیجه next() باید به شکل {done: Boolean, value: any} باشد که done=true به معنی پایان حلقه‌زدن است، در غیر این صورت value مقدار بعدی خواهد بود.

اینجا پیاده‌سازی کامل range را به همراه ملاحظات داریم:

let range = {
  from: 1,
  to: 5
};

// 1. در ابتدا این متد صدا زده می‌شود for..of با صدازدن
range[Symbol.iterator] = function() {

  // :این متد شیء حلقه‌زننده را برمی‌گرداند...
  // 2. فقط با این حلقه‌زننده کار می‌کند، که از آن مقدار بعدی را درخواست می‌کند for..of ،همینطور رو به جلو
  return {
    current: this.from,
    last: this.to,

    // 3. فراخوانی می‌شود for..of در هر دور حلقه توسط next()
    next() {
      // 4. برگرداند {done:..., value:...} این متد باید مقدار را به عنوان یک شیء
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

// !حالا کار می‌کند
for (let num of range) {
  alert(num); // 1, then 2, 3, 4, 5
}