انواع loop برای آرایه‌ها: for و for-in و ()forEach. و for-of

حمیدرضا مهدوی پناه · خواندن 5 دقیقه · 980 کلمه · ۱۴۰۰/۲/۹

در این نوشته چهار روش مختلف برای استفاده از حلقه‌ها روی آرایه‌ها رو مقایسه می‌کنیم.

  • حلقه‌ی for:
1for (let index = 0; index < someArray.length; index++) {
2  const elem = someArray[index]
3  // ···
4}
  • حلقه‌ی for-in:
1for (const key in someArray) {
2  console.log(key)
3}
  • متد ()forEach. از کلاس Array:
1someArray.forEach((elem, index) => {
2  console.log(elem, index)
3})
  • حلقه‌ی for-of:
1for (const elem of someArray) {
2  console.log(elem)
3}

for-of در اغلب موارد بهترین انتخابه. در ادامه میبینم چرا.


۱ - حلقه‌ی for [جاوااسکریپت ES1] 🔗

حلقه‌ی ساده for در جاوااسکریپت قدمت داره و از نسخه‌ی ۱ ECMAScript وجود داشته. حلقه زیر اندیس و مقدار هر عنصر آرایه arr رو چاپ میکنه:

 1const arr = ["a", "b", "c"]
 2arr.prop = "property value"
 3
 4for (let index = 0; index < arr.length; index++) {
 5  const elem = arr[index]
 6  console.log(index, elem)
 7}
 8
 9// Output:
10// 0, 'a'
11// 1, 'b'
12// 2, 'c'

نقاط مثبت و منفی این حلقه چیه؟

  • کاملا همه‌کارس اما از طرفی وقتی فقط میخوایم روی آرایه حلقه بزنیم، نوشتنش طولانی و پرجزئیاته.
  • اگه نمیخوایم از اولین عنصر آرایه شروع کنیم، این حلقه خیلی بدردبخوره. هیچکدوم از روش‌های دیگه این امکان رو بهمون نمیدن.

۲ - حلقه‌ی for-in [جاوااسکریپت ES1] 🔗

حلقه‌ی for-in به اندازه‌ی حلقه‌ی for قدمت داره. حلقه‌ی زیر کلید‌های آرایه‌ی arr رو چاپ میکنه:

 1const arr = ["a", "b", "c"]
 2arr.prop = "property value"
 3
 4for (const key in arr) {
 5  console.log(key)
 6}
 7
 8// Output:
 9// '0'
10// '1'
11// '2'
12// 'prop'

for-in انتخاب خوبی برای حلقه‌زدن روی آرایه‌های نیست، چون:

  • کلیدهای property رو مرور میکنه و نه مقادیر رو.
  • اندیس‌های آرایه وقتی به صورت کلید‌های property دیده‌میشن، به جای این که عدد باشن، از جنس string هستن. (اطلاعات بیشتر درمورد نحوه کار عناصر آرایه‌ها)
  • به جای اندیس عناصر آرایه، تمام کلید‌های property که قابل شمارش هستن (enumerable) رو مرور میکنه (هم اونایی که مال خود آرایه هستن و هم اونایی که به ارث رسیدن).

این ویژگی for-in که تمام propertyهای به ارث‌رسیده رو مرور میکنه یه جا کاربرد داره: زمانی که میخوایم روی propertyهای قابل شمارش یه object حلقه بزنیم. اما حتی در اون مورد هم حلقه زدن روی زنجیره prototype به شکل دستی بهتره، چون کنترل بیشتری در اختیارمون میذاره.

۳ - متد ()forEach. [جاوااسکریپت ES5] 🔗

با توجه به این که نه حلقه‌ی for و نه حلقه‌ی for-in انتخاب‌های خوبی برای حلقه‌زدن روی آرایه‌ها نیستن، یه متد کمکی در ECMAScript 5 معرفی شد: ()Array.prototype.forEach:

 1const arr = ["a", "b", "c"]
 2arr.prop = "property value"
 3
 4arr.forEach((elem, index) => {
 5  console.log(elem, index)
 6})
 7
 8// Output:
 9// 'a', 0
10// 'b', 1
11// 'c', 2

این متد خیلی راحته: بدون نیاز به انجام کار زیاد ، هم دسترسی به اندیس و هم به عناصر آرایه رو میده. تابع‌های فِلشی (Arrow functions) که در ES6 معرفی شدن، این متد رو از قبل هم زیباترش کرده.

معایب اصلی ()forEach. از این قراره:

  • امکان استفاده از await در «بدنه» این نوع حلقه وجود نداره.
  • از حلقه‌ی ()forEach. نمیشه زودتر از موعد خارج شده. درصورتیکه در حلقه‌های for میشه از break استفاده کرد.

۱.۳ - خارج شدن از ()forEach. - یه راه حل 🔗

یه راه حل برای خارج شدن از این حلقه هست: استفاده از ()some. که روی تمام عناصر آرایه حلقه میزنه و زمانی که تابع callback یه مقدار truthy (یه مقداری که بشه به معنی true تفسیرش کرد) رو برگردونه، متوقف میشه.

 1const arr = ["red", "green", "blue"]
 2arr.some((elem, index) => {
 3  if (index >= 2) {
 4    return true // از حلقه خارج میشه
 5  }
 6  console.log(elem)
 7  // رو برمیگردونه undefined تابع به طور ضمنی مقدار
 8  // هست حلقه ادامه پیدا میکنه falsy که چون یه مقدار
 9})
10
11// Output:
12// 'red'
13// 'green'

۴ - حلقه‌ی for-of [جاوااسکریپت ES6] 🔗

این حلقه در ECMAScript 6 اضافه شد:

 1const arr = ["a", "b", "c"]
 2arr.prop = "property value"
 3
 4for (const elem of arr) {
 5  console.log(elem)
 6}
 7// Output:
 8// 'a'
 9// 'b'
10// 'c'

for-of برای حلقه زدن روی آرایه‌ها خیلی خوبه، چون:

  • روی عناصر آرایه تکرار میشه.
  • داخلش میتونیم از await استفاده کنیم.
    • و اگه نیاز پیدا کنیم خیلی راحت میتونیم به for-await-of کوچ کنیم.
  • میتونیم از break و continue استفاده کنیم - حتی در اسکوپ (scope) های بیرونی‌تر.

۱.۴ - حلقه‌ی for-of و آبجکت‌های iterable 🔗

یه مزیت اضافه‌ی for-of اینه که با اون نه فقط روی آرایه‌ها بلکه روی آبجکت‌های iterable هم میشه حلقه زد - برای مثال، روی Mapها:

1const myMap = new Map().set(false, "no").set(true, "yes")
2for (const [key, value] of myMap) {
3  console.log(key, value)
4}
5
6// Output:
7// false, 'no'
8// true, 'yes'

حلقه زدن روی myMap جفت [key, value] رو برمیگردونه که در کد بالا از روش destructre کردن برای دسترسی مستقیم به مقادیر این جفت استفاده کردیم.

۲.۴ - حلقه‌ی for-of و اندیس‌های آرایه 🔗

متد ()keys. در آرایه‌ها، یه iterable روی اندیس‌های آرایه رو برمیگردونه:

1const arr = ["chocolate", "vanilla", "strawberry"]
2
3for (const index of arr.keys()) {
4  console.log(index)
5}
6// Output:
7// 0
8// 1
9// 2

۳.۴ - حلقه‌ی for-of و دسترسی همزمان به مقادیر و اندیس‌ها با ()entries. 🔗

متد ()entries. در آرایه‌ها یه iterable روی جفت‌های [index, value] برمیگردونه. با استفاده از for-of و destructuring خیلی راحت میتونیم روی مقادیر و اندیس‌های آرایه به طور همزمان حلقه بزنیم:

1const arr = ["chocolate", "vanilla", "strawberry"]
2
3for (const [index, value] of arr.entries()) {
4  console.log(index, value)
5}
6// Output:
7// 0, 'chocolate'
8// 1, 'vanilla'
9// 2, 'strawberry'

۵ - نتیجه‌گیری 🔗

همونطور که دیدیم، وقتی معیار کاربرد باشه حلقه‌ی for-of از بقیه‌ی روش‌ها بهتره.

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


منبع: Looping over Arrays: for vs. for-in vs. .forEach() vs. for-of از وبلاگ 2ality - JavaScript and more