الرجوع الي الدرس

الأهمية: 5

جيش من الدوال

تصنع الشيفرة الآتية مصفوفة من مُطلقي النار ‎shooters‎.

يفترض أن تكتب لنا كلّ دالة رقم هويّتها، ولكن ثمّة خطب ما …

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function() { // دالة مُطلق النار
      alert( i ); // المفترض أن ترينا رقمها
    };
    shooters.push(shooter);
    i++;
  }

  return shooters;
}

let army = makeArmy();

army[0](); // مُطلق النار بالهويّة 0 يقول أنّه 10
army[5](); // ‫مُطلق النار بالهويّة 5 يقول أنّه 10...
// ... كلّ مُطلقي النار يقولون 10 بدل هويّاتهم 0 فَـ 1 فَـ 2 فَـ 3...

لماذا هويّة كلّ مُطلق نار نفس البقية؟ أصلِح الشيفرة لتعمل كما ينبغي أن تعمل.

افتح sandbox بالإختبارات.

لنُجري مسحًا شاملًا على ما يجري في ‎makeArmy‎، حينها سيظهر لنا الحل جليًا.

  1. تُنشئ مصفوفة ‎shooters‎ فارغة:

    let shooters = [];

. تملأ المصفوفة في حلقة عبر ‎shooters.push(function...)‎.

كلّ عنصر هو دالة، بهذا تكون المصفوفة الناتجة هكذا:


```js no-beautify
shooters = [
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); },
  function () { alert(i); }
];
```
  1. تُعيد الدالة المصفوفة.

لاحقًا، يستلم استدعاء ‎army[5]()‎ العنصر ‎army[5]‎ من المصفوفة، وهي دالة فيستدعيها.

الآن، لماذا تعرض كلّ هذه الدوال نفس الناتج؟

يعود ذلك إلى عدم وجود أيّ متغير محلي باسم ‎i‎ في دوال ‎shooter‎. فحين تُستدعى هذه الدالة تأخذ المتغير ‎i‎ من البيئة المُعجمية الخارجية.

وماذا ستكون قيمة ‎i‎؟

لو رأينا مصدر القيمة:

function makeArmy() {
  ...
  let i = 0;
  while (i < 10) {
    let shooter = function() { // shooter function
      alert( i ); // should show its number
    };
    ...
  }
  ...
}

كما نرى… «تعيش» القيمة في البيئة المُعجمية المرتبطة بدورة ‎makeArmy()‎ الحالية. ولكن متى استدعينا ‎army[5]()‎، تكون دالة ‎makeArmy‎ قد أنهت مهمّتها فعلًا وقيمة ‎i‎ هي آخر قيمة، أي ‎10‎ (قيمة نهاية حلقة ‎while‎).

وبهذا تأخذ كلّ دوال ‎shooter‎ القيمة من البيئة المُعجمية الخارجية، ذات القيمة الأخيرة ‎i=10‎.

يمكن أن نُصلح ذلك بنقل تعريف المتغير إلى داخل الحلقة:

function makeArmy() {

  let shooters = [];

  for(let i = 0; i < 10; i++) {
    let shooter = function() { // دالة مُطلق النار
      alert( i );  // المفترض أن ترينا رقمها
    };
    shooters.push(shooter);
  }

  return shooters;
}

let army = makeArmy();

army[0](); // 0
army[5](); // 5

الآن صارت تعمل كما يجب، إذ في كلّ مرة تُنفّذ كتلة الشيفرة في ‎for (let i=0...) {...}‎، يُنشئ المحرّك بيئة مُعجمية جديدة لها فيها متغير ‎i‎ المناسب لتلك الكتلة.

إذًا لنلخّص: قيمة ‎i‎ صارت «تعيش» أقرب للدالة من السابق. لم تعد في بيئة ‎makeArmy()‎ المُعجمية بل الآن في تلك البيئة المخصّصة لدورة الحلقة الحالية. هكذا صارت تعمل كما يجب.

أعدنا كتابة الشيفرة هنا وعوّضنا ‎while‎ بحلقة ‎for‎.

يمكننا أيضًا تنفيذ حيلة أخرى. لنراها لفهم الموضوع أكثر:

function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let j = i;
    let shooter = function() { // دالة مُطلق النار
      alert( j ); // (*) المفترض أن ترينا رقمها
    };
    shooters.push(shooter);
    i++;
  }

  return shooters;
}

let army = makeArmy();

army[0](); // 0
army[5](); // 5

كما حلقة ‎for‎، فحلقة ‎while‎ تصنع بيئة مُعجمية جديدة لكلّ دورة، وهكذا نتأكّد بأن تكون قيمة ‎shooter‎ صحيحة.

باختصار ننسخ القيمة ‎let j = i‎ وهذا يصنع المتغير ‎j‎ المحلي داخل الحلقة وينسخ قيمة ‎i‎ إلى نفسه. تُنسخ الأنواع الأولية «حسب قيمتها» By value، لذا بهذا نأخذ نسخة كاملة مستقلة تمامًا عن ‎i‎، ولكنّها مرتبطة بالدورة الحالية في الحلقة.

افتح الحل الإختبارات في sandbox.