مقدمه

حلقه‌ها (loops) یکی دیگر از ساختارهای کلیدی زبان‌های برنامه‌نویسی هستند. وقتی مجموعه‌ای از دستورات باید به صورت تکراری اجرا شوند، می‌توانیم به جای n بار نوشتن آن دستورات، از یک حلقه استفاده کنیم. حلقه‌های while و for مهمترین حلقه‌های تکرار در جاوااسکریپت هستند.

حلقه‌های while و do while

گزاره‌های while برای اجرای تکراری دستورات کاربرد دارند. یک گزاره‌ی while دارای فرم کلی زیر است.

while (condition) {
  // code of loop body
}

تا زمانی که condition دارای یک مقدار truthy باشد، دستورات بدنه‌ی حلقه اجرا می‌شوند. اگر بدنه‌ی حلقه شامل فقط یک دستور باشد، می‌توانیم از آکلادها صرف‌نظر کنیم؛ البته این کار توصیه نمی‌شود.

Copy Icon loops.js
let i = 1;

while (i <= 5) {
  console.log(i);
  i++;
}

در اینجا وقتی برنامه به گزاره‌ی while می‌رسد، متغیر i دارای مقدار 1 است. پس، شرط حلقه برقرار است و بنابراین، دسورات بدنه‌ی حلقه اجرا می‌شوند. نتیجتاً مقدار 1 چاپ شده و یک واحد به i اضافه می‌شود. حالا i دارای مقدار 2 است و باز هم شرط حلقه برقرار است و بنابراین، بدنه‌ی حلقه یک بار دیگر اجرا می‌شود. این روال ادامه دارد تا زمانی که در پنجمین تکرار، مقدار 5 چاپ شود و i به مقدار 6 برسد. در این وضع، شرط حلقه دیگر برقرار نیست و بنابراین، برنامه از حلقه خارج می‌شود. پس، خروجی این کد به صورت زیر خواهد بود.

1 
2 
3 
4 
5
          

while یک حلقه‌ی pre-test است؛ یعنی شرط حلقه قبل از بدنه‌ی حلقه قرار دارد. بنابراین، این احتمال وجود دارد که دستورات بدنه‌ی حلقه اصلاً اجرا نشوند. اما یک ورژن post-test از این حلقه داریم که do while نام دارد. سینتکس کلی یک گزاره‌ی do while به صورت زیر است.

do {
  // code of loop body
} while (condition);

با توجه به اینکه در گزاره‌های do while، شرط حلقه بعد از بدنه قرار دارد، دستورات بدنه لااقل یک بار اجرا می‌شوند. کد زیر باعث چاپ اعداد 1 تا 5 در خروجی می‌شود.

Copy Icon loops.js
let i = 1;

do {
  console.log(i);
  i++;
} while (i <= 5);

در مثال‌های بالا، شرط حلقه یک عبارت بولین است اما امکان استفاده از عبارت‌های غیر بولین هم وجود دارد. دقت کنید که ما شرط اجرای دستورات بدنه را truthy بودن عبارت عنوان کردیم. پس، اگر عبارت غیر بولینی به حلقه داده شود، به طور ضمنی به بولین تبدیل می‌شود و اگر نتیجه true باشد، بدنه‌ی حلقه اجرا می‌شود. مثال زیر را ببینید.

Copy Icon loops.js
let i = 5;

while (i) {
  console.log(i);
  i--;
}

نتیجه‌ی اجرای این کد به صورت زیر خواهد بود.

5 
4 
3
2
1
          

حلقه for

حلقه‌ی for کمی پیچیده‌تر است اما بیشتر از سایر حلقه‌ها مورد استفاده قرار می‌گیرد. فرم کلی یک حلقه‌ی for به صورت زیر است.

for (counter; condition; iterator) {
  // code to run
}

همانطور که می‌بینید، ابتدا کلمه کلیدی for و سپس یک جفت پرانتز ایجاد شده و درون پرانتزها سه آیتم داریم که با سمی‌کالن از هم جدا شده‌اند. آیتم اول معمولاً متغیری است که روی یک مقدار عددی تنظیم شده و شمارنده‌ی تعداد دفعات اجرای حلقه محسوب می‌شود. به همین دلیل آن را counter نامیده‌ایم. آیتم دوم یعنی condition یک عبارت شرطی است که تعیین کننده‌ی زمان توقف حلقه است. در واقع، حلقه تا زمانی ادامه پیدا می‌کند که این شرط برقرار باشد. آیتم سوم نیز iterator یا تکرارگر نامیده شده و با هر بار اجرای حلقه مقدار متغیر شمارنده را افزایش یا کاهش می‌دهد تا بعد از هر بار اجرای حلقه یک گام به نقطه‌ی پایان حلقه نزدیک شویم. در پایان نیز یک جفت آکلاد قرار دارد که دستوراتی را که در هر تکرار حلقه باید انجام شوند، شامل است.

در مثال زیر از یک حلقه‌ی for برای چاپ اعداد 1 تا 5 استفاده شده است.

Copy Icon loops.js
for (let i = 1; i <= 5; i++) {
  console.log(i);
}

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

نقش break و continue

در حالت نرمال، یک حلقه زمانی به پایان می‌رسد که عبارت شرط حلقه به یک falsy تبدیل شود. اما با استفاده از کلمه کلیدی break می‌توانیم هر وقت که بخواهیم، حلقه را متوقف کنیم.

Copy Icon loops.js
for (let i = 0; i < 10; i++) {
  if (i === 3) {
    break;
  }
  console.log(i);
}

در این مثال، وقتی متغیر i به مقدار 3 برسد، دستور break اجرا شده و حلقه متوقف می‌شود. بنابراین، فقط اعداد 0، 1 و 2 در خروجی چاپ می‌شوند.

continiue را می‌توانیم یک ورژن لایت از break بدانیم. چون break کل حلقه را متوقف می‌کند و سراغ ادامه‌ی دستورات برنامه می‌رود اما continue تکرار جاری را متوقف می‌کند و سراغ تکرار بعدی می‌رود. در مثال زیر، با استفاده از continue ترتیبی داده‌ایم که اعداد فرد یک‌رقمی چاپ شوند.

Copy Icon loops.js
for (let i = 0; i < 10; i++) {

  if (i % 2 == 0) continue;
            
  console.log(i); 
}

برای مقادیر زوج متغیر i، دستور continue تکرار جاری را متوقف می‌کند و حلقه را به تکرار بعدی می‌فرستد.

لیبل برای break/continue

وقتی در حلقه‌های تودرتو شده از break یا continue استفاده می‌کنیم، داخلی‌ترین حلقه ملاک عمل خواهد بود. برای روشن شدن این موضوع، مثال زیر را ببینید.

Copy Icon functions.js
for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i == 2) break; // *
    console.log(`point (${i}, ${j})`);
  }
            
  console.log(i);
}

اگر کد بالا را بدون خطی که با * مشخص شده، اجرا کنیم، زوج‌های (0, 0) تا (2, 2) در خروجی چاپ می‌شوند. اما وجود این خط کد باعث می‌شود که وقتی i به مقدار 2 برسد، حلقه متوقف شود. اما کدام حلقه؟ حلقه‌ی داخلی یا بیرونی؟ برای دریافت جواب این سؤال، برنامه را اجرا می‌کنیم.

point (0, 0)
point (0, 1)
point (0, 2)
0
point (1, 0)
point (1, 1)
point (1, 2)
1
2
          

این خروجی نشان می‌دهد که دستور break روی حلقه‌ی داخلی اعمال شده، نه بیرونی. چون وقتی i به مقدار 2 رسیده، چاپ زوج‌ها متوقف شده اما گزاره‌ی console.log(i) در حلقه‌ی بیرونی همچنان اجرا شده و مقدار 2 را در خط آخر چاپ کرده است.

اما اگر بخواهیم دستور break منجر به خروج از همه‌ی حلقه‌ها شود، باید روی بیرونی‌ترین حلقه اعمال شود. اینجاست که لیبل‌ها به کمک ما می‌آیند. می‌توانیم روی حلقه‌ها برچسب یا لیبل بزنیم و در واقع، نامی به آنها اختصاص دهیم. سپس، هنگام استفاده از دستور break (یا continue) نام حلقه‌ی مورد نظر را مشخص کنیم.

Copy Icon functions.js
outer: for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    if (i == 2) break outer;
    console.log(`point (${i}, ${j})`);
  }
            
  console.log(i);
}

در اینجا با استفاده از روشی که می‌بینید، روی حلقه‌ی بیرونی لیبل زده‌ایم و سپس، از این لیبل برای مشخص کردن حلقه‌ی مورد اشاره‌ی break استفاده کرده‌ایم. این کار باعث می‌شود که بیرونی‌ترین حلقه متوقف شود و در نتیجه از کل حلقه‌ها خارج شویم. بنابراین، این بار خروجی به صورت زیر خواهد بود.

point (0, 0)
point (0, 1)
point (0, 2)
0
point (1, 0)
point (1, 1)
point (1, 2)
1