المستوى: مبتدئ — لو كتبت قبل كده setTimeout(fn, 0) وافتكرت إنها هتشتغل في نفس اللحظة، المقال ده هيوريك ليه بتتأخّر، وإزاي جافاسكريبت بتعمل مليون حاجة في نفس الوقت وهي أصلًا بتشتغل بخيط واحد بس.
الـ Event Loop في جافاسكريبت مش سحر، ده طابور منظّم
جافاسكريبت single-threaded، يعني بتنفّذ أمر واحد في اللحظة. ومع ذلك بتقدر تحمّل صورة، وتستنى رد من سيرفر، وتستجيب لكليك المستخدم، كل ده من غير ما الصفحة تتجمّد. اللي بيخلّي ده ممكن اسمه الـ Event Loop.
المشكلة باختصار
كتير من المبتدئين بيتصدموا لما يشوفوا كود زي ده يطبع نتيجة مش متوقّعة. الافتراض إن الأوامر بتتنفّذ من فوق لتحت بالترتيب. ده صح، لكن مش كل أمر بيتنفّذ في نفس اللحظة. بعض الأوامر بتتأجّل، والـ Event Loop هو اللي بيقرّر امتى ترجع.
الأول مثال بسيط: نادل واحد في مطعم
تخيّل مطعم فيه نادل واحد بس بيخدم خمس ترابيزات. لو النادل خد أوردر من ترابيزة، وبعدين وقف جنب المطبخ مستنّي الأكل يجهز قبل ما يروح لأي حد تاني، المطعم هيتشلّ. الأربع ترابيزات التانية هيقعدوا يستنّوا فاضيين.
النادل الشاطر بيعمل حاجة تانية. بياخد الأوردر، بيسلّمه للمطبخ، وبيروح فورًا لترابيزة تانية. لما المطبخ يخلّص، بيناديه، فيرجع يقدّم الأكل. النادل لسه واحد، بس مبيقفش يستنّى. ده بالظبط اللي بتعمله جافاسكريبت.
النادل هو الخيط الوحيد بتاع جافاسكريبت. المطبخ هو الأشياء اللي بتشتغل في الخلفية (زي الشبكة أو المؤقّتات). ونداء المطبخ للنادل لما الأكل يجهز، ده الـ callback اللي بيدخل الطابور.
دلوقتي الشرح العلمي الدقيق
في المتصفح فيه أربع قطع بتشتغل مع بعض:
- مكدّس الاستدعاء (Call Stack): المكان اللي بيتنفّذ فيه الكود سطر ورا سطر. حاجة واحدة بس فوق المكدّس في أي لحظة.
- واجهات المتصفح (Web APIs): حاجات المتصفح بيتكفّل بيها بره الخيط الرئيسي، زي
setTimeoutوfetch. - طابور المهام (Callback Queue): لما مهمة خلفية تخلّص، الـ callback بتاعها بيقف في طابور مستنّي دوره.
- حلقة الأحداث (Event Loop): بتراقب المكدّس. لحظة ما المكدّس يفضى، بتسحب أول callback من الطابور وتحطّه في المكدّس ليتنفّذ.
القاعدة الذهبية: الـ Event Loop مبتحطّش حاجة جديدة في المكدّس إلا لما المكدّس يبقى فاضي تمامًا. عشان كده أي callback بيستنّى لغاية ما الكود الأساسي كله يخلّص.
الكود اللي بيوريك ده بعينك
جرّب الكود ده في الـ console بتاع أي متصفح:
console.log("1");
setTimeout(function () {
console.log("2");
}, 0);
console.log("3");
// الناتج الفعلي:
// 1
// 3
// 2