for...of في JavaScript

من موسوعة حسوب

التعبير البرمجي for...of يمر على الكائنات التي يمكن الدوران على عناصرها (iterable objects) بما في ذلك الكائن Array و Map و Set و String و TypedArray و Arguments، وتنفيذ تعابير برمجية معيّنة على قيمة كل خاصية من خاصيات تلك الكائنات.

البنية العامة

for (variable of iterable) {
  statement
}

variable

ستُنسَد قيمة الخاصية مختلفة لهذا المتغير في كل دورة.

iterable

خاصيات الكائن القابلة للدوران (iterable properties).

الوصف

الفرق بين حلقتَي التكرار for...of و for...in

التعبيران البرمجيان for...in و for...of يُستخدَمان للدوران على شيءٍ ما، لكن الاختلاف الرئيسي بينهما هو ما هي الأشياء التي تدور حلقة التكرار عليها.

فالتعبير for...in يدور على الخاصيات الكائن القابلة للإحصاء (enumerable properties) بترتيب إدخالها الأصلي.

أما التعبير for...of فسيدور على البيانات التي يُعرِّفها كائن iterable على أنها قابلة للدوران.

هذا المثال يوضِّح الفرق بين استخدام حلقة for...of و for...in عند استخدامهما على كائن Array:

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';

for (let i in iterable) {
  console.log(i); // 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // 0, 1, 2, "foo"
  }
}

for (let i of iterable) {
  console.log(i); // 3, 5, 7
}

سنُجزِّئ المثال السابق إلى أقسام ليسهل فهمها:

Object.prototype.objCustom = function() {};
Array.prototype.arrCustom = function() {}; 

let iterable = [3, 5, 7]; 
iterable.foo = 'hello';

سيرث كل كائن الخاصية objCustom وكل كائن Array سيرث الخاصية arrCustom وذلك بسبب إضافة تلك الخاصيات إلى Object.prototype و Array.prototype؛ وبالتالي سيرثها الكائن iterable.

for (let i in iterable) {
  console.log(i); // 0, 1, 2, "foo", "arrCustom", "objCustom" 
}

حلقة التكرار السابقة ستعرض الخاصيات القابلة للإحصاء (enumerable properties) بترتيب إضافتها الأصلي، ولن تؤدي إلى إظهار عناصر المصفوفة 3 أو 5 أو 7 أو hello لأنها لا تُعدّ خاصياتٍ قابلةً للإحصاء؛ لكنها ستعرض فهارس المصفوفة والخاصية arrCustom و objCustom، لمزيدٍ من المعلومات راجع صفحة for...in.

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // 0, 1, 2, "foo"
  }
}

حلقة التكرار السابقة تشبه أوّل حلقة تكرار، لكنها تستخدم الدالة hasOwnProperty()‎ للتأكد إذا كانت الخاصية تابعةً للكائن مباشرةً وليست خاصيةً موروثةً؛ لذا ستُعرَض الخاصيات 0 و 1 و 2 و foo لأنها تابعة مباشرةً للكائن، ولن تُعرَض الخاصيات arrCustom و objCustom لأنها موروثة.

for (let i of iterable) {
  console.log(i); // 3, 5, 7 
}

حلقة التكرار السابقة تمر وتعرض القيم التي يُعرِّف كائن iterable أنه يمكن المرور عليها، وهي عناصر المصفوفة 3 و 5 و 7 ولن تُعرَض خاصيات الكائن قط.

إنهاء التكرارات

يمكن إنهاء عملية الدوران في حلقات for...of باستخدام التعابير البرمجية break أو continue أو throw أو return:

function* foo(){ 
  yield 1; 
  yield 2; 
  yield 3; 
}; 

for (let o of foo()) { 
  console.log(o); 
  break; // إغلاق التكرار
}

أمثلة

المرور على Array

let iterable = [10, 20, 30];

for (let value of iterable) {
  value += 1;
  console.log(value);
}
// 11
// 21
// 31

يمكنك أيضًا استخدام const بدلًا من let، لكن حينئذٍ لن تتمكن من إعادة إسناد قيمة للمتغير داخل الكتلة:

let iterable = [10, 20, 30];

for (const value of iterable) {
  console.log(value);
}
// 10
// 20
// 30

المرور على String

let iterable = 'boo';

for (let value of iterable) {
  console.log(value);
}
// "b"
// "o"
// "o"

المرور على TypedArray

let iterable = new Uint8Array([0x00, 0xff]);

for (let value of iterable) {
  console.log(value);
}
// 0
// 255

المرور على Map

let iterable = new Map([['a', 1], ['b', 2], ['c', 3]]);

for (let entry of iterable) {
  console.log(entry);
}
// ['a', 1]
// ['b', 2]
// ['c', 3]

for (let [key, value] of iterable) {
  console.log(value);
}
// 1
// 2
// 3

المرور على Set

let iterable = new Set([1, 1, 2, 2, 3, 3]);

for (let value of iterable) {
  console.log(value);
}
// 1
// 2
// 3

المرور على كائن Arguments

(function() {
  for (let argument of arguments) {
    console.log(argument);
  }
})(1, 2, 3);

// 1
// 2
// 3

المرور على قائمة عقد في DOM

يمكن المرور على قائمة عقد في DOM مثل NodeList، فالمثال الآتي يُضيف الصنف read إلى الفقرات <p> التي تكون أبناءً مباشرين للعنصر <article>:

let articleParagraphs = document.querySelectorAll('article > p');

for (let paragraph of articleParagraphs) {
  paragraph.classList.add('read');
}

المرور على المولِّدات

function* fibonacci() { // دالة مولِّدة
  let [prev, curr] = [0, 1];
  while (true) {
    [prev, curr] = [curr, prev + curr];
    yield curr;
  }
}

for (let n of fibonacci()) {
  console.log(n);
  // إيقاف السلسلة عند العدد 1000
  if (n >= 1000) {
    break;
  }
}

المرور على الكائنات التي تُطبِّق بروتوكول iterable

يمكن أيضًا استخدام حلقة التكرار for...of للمرور على الكائنات التي تُطبِّق بروتوكول iterable صراحةً:

var iterable = {
  [Symbol.iterator]() {
    return {
      i: 0,
      next() {
        if (this.i < 3) {
          return { value: this.i++, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (var value of iterable) {
  console.log(value);
}
// 0
// 1
// 2

دعم المتصفحات

الميزة Chrome Firefox Internet Explorer Opera Safari
الدعم الأساسي 38 13 غير مدعومة 25 8
إغلاق التكرارات 51 53 غير مدعومة مدعومة مدعومة

مصادر ومواصفات