Skip to content

سؤال جافاسكريبت تطرحه قوقل وأمازون في مقابلات العمل

سؤال جافاسكريبت تطرحه قوقل وأمازون في مقابلات العمل

11 يناير 2018 | 00:00

هذا الموضوع سيكون شرحا مختصر، مرفوقا ببعض الحلول، لواحد من أكثر الأسئلة التي تعرض على مطوري الجافاسكريبت في مقابلات العمل مع كبريات الشركات التقنية مثل غوغل.

هذا السؤال أو الإختبار البسيط يكون في العادة على هذا النحو :

// السؤال : ما هي المخرجات النهائية لهذه الشفرة البرمجية ؟
var arr = ["a", "b", "c", "d"];
for (var i = 0; i < arr.length; i++) {
  setTimeout(function () {
    console.log("Index: " + i + ", element: " + arr[i]);
  }, 3000);
}

السؤال يتعرض لعدد من المفاهيم الأساسية للغة البرمجة جافاسكريبت، مثل مفهوم Scope و Closure.

النتيجة النهائية للسؤال ستكون طباعة ما يلي 4 مرات :

Index: 4, element: undefined(4 مرات).

إذا لم تكن هذه هي النتيجة التي توقعتها، فتابع الموضوع حتى النهاية لتفهم أكثر مالذي حدث :)

لماذا هذا السؤال يطرح بكثرة في مقابلات العمل ؟

كما قلنا، هذا السؤال يحوي في ثناياه إجابات على عدد من أساسيات الجافاسكريبت. في حالات كثيرة ستجد نفسك في سيناريوهات مشابهة في مشاريع برمجية عندما تضطر للقيام بمهام غير متزامنة (Asynchronous) داخل حلقات For.

أحد مستخدمي منصة reddit أكد بدوره أنه واجه هذا السؤال في مقابلة للعمل مع شركة أمازون. هذا إضافة لعدد كبير جدا ممن قالوا بأن هذا السؤال عرض عليهم في مقابلات مع Google.

الحلول المقترحة

قبل تقديم الحلول، نريد أن نفهم بالتحديد مالذي حدث ؟

باختصار، دالة setTimeout قامت بإنشاء دالة داخلية (closure)، هذه الأخيرة تستطيع الوصول للنطاق الخارجي (External scope) وأقرب نطاق خارجي لها هو الحلقة For التي قامت بتعريف متغير اسمه “i”. بعد ثلاث ثواني، الحلقة كانت قد نُفِّذَت وبالتالي كل ما في داخل setTimeout سيأخذ قيمة “i” على أنها آخر قيمة أخذها الأخير قبل إنهاء الحلقة. هذه القيمة كما نعرف هي 4 وبالتالي قيمة [4]arr ستكون undefined لأن آخر عنصر في المصفوفة هو [3]arr ويساوي “d” طبعا :D

هناك حلين لهذه الإشكالية، أولهما من المدرسة القديمة، حيث يتم استخدام “دالة أم” تقوم بإرجاع دالة أخرى. الغرض الوحيد من استخدام “الدالة الأم” هو إنشاء نطاق جديدا (New Scope) يحمل قيمة “i” الحالية وفي كل مرة تقوم الدالة الأم بإرجاع مرجع (reference) جديد على شكل دالة تقوم بطبع قيمة “i”.

var show = function(n){

return function(){
  console.log('Index: ' + n + ', element: ' + arr[n]);
}

}; var arr = [“a”, “b”, “c”, “d”]; for (var i = 0; i < arr.length; i++) { setTimeout(show(i), 3000); }

أو يمكن القيام بالأمر مباشروة باستخدام ما يعرف ب Immediately Invoked Function Expression أو اختصارا IIFE :

var arr = [“a”, “b”, “c”, “d”]; for (var i = 0; i < arr.length; i++) {

setTimeout((function(i_local) { return function() { console.log(‘Index: ’ + i + ’, element: ’ + arr[i]); } })(i), 3000);

}

والنتيجة الآن كما المتوقع :

“Index: 0, element: a” “Index: 1, element: b” “Index: 2, element: c” “Index: 3, element: d”

أما الحل الأسهل فهو استخدام let عند التصريح بالمتغير “i”. ظهر let في إصدار ES6 لجافاسكريبت، ويختلف عن var كونه يقوم بإنشاء binding جديد في كل تكرار للحلقة (loop)، مما يسمح لدالة Closure بأخذ وتذكر قيمة “i” عند كل تكرار.

var arr = [“a”, “b”, “c”, “d”]; for (let i = 0; i < arr.length; i++) {

setTimeout(function(){

console.log(‘Index: ’ + i + ’, element: ’ + arr[i]);

}, 3000);

}

وهذا هو الرابط للمزيد من التفاصيل حول هذه الجزئية. (exploringjs.com)


الموضوع للأمانة شائك، وهو من أكثرها غموضا في جافاسكريبت بالنسبة للقادمين الجدد والمتوسطين. شخصيا، أخذ مني هذا الموضوع وقتا طويلا حتى أستوعبه جيدا.

هذه مجموعة من الروابط للمزيد من التفاصيل والغوص أكثر في هذا البحر العميق.

عيسى محمد علي
عيسى محمد علي
مطور ويب متخصص في الواجهات الأمامية، أحب التدوين وإغناء المحتوى التقني للغة الضاد وهذا كان السبب الرئيسي في إنشائي لمدونة توتومينا.