المستوى: مبتدئ
Hoisting في JavaScript: ليه var x شغّال قبل تعريفه و let x بيرمي خطأ
لو كتبت console.log(x); var x = 5; الكود بيشتغل ويطبع undefined بدون أي خطأ. لو غيّرت var لـ let، الكود بيقع بـ ReferenceError. الفرق ده اسمه Hoisting، وفهمه بيوفّر عليك ساعات بحث في bugs مش منطقية على إطلاق.
المشكلة باختصار
كتير من المبتدئين بيشوفوا رسالة ReferenceError: Cannot access 'x' before initialization ومش فاهمين السبب — خصوصًا إن نفس الكود بـ var كان شغّال طبيعي قبل ما يحوّلوا الكود لـ let. السبب إن JavaScript بيمر على الكود بتاعك مرّتين، والمرور الأول بيرفع تعريفات المتغيرات لأعلى الـ scope. هنا هتفهم بالظبط ايه اللي بيحصل، ولاحظ الفرق بين var و let و const والـ functions.
تخيّل الموقف بمثال بسيط
تخيّل إنك بتسافر بكرة الصبح. الليلة اللي قبل، بتفتح الحقيبة وتكتب على ورقة كل اللي محتاجه: «جواز السفر، التذكرة، الشاحن، الـ headphones». لسه ما حطّيتش الحاجات نفسها جوا الحقيبة — بس الأسماء معروفة عندك. لما الصبح يجي، بتمشي على الورقة وتحط كل حاجة في مكانها.
JavaScript بيعمل نفس الفكرة بالظبط. أول ما بيشوف الكود بتاعك، بيمر مرور سريع يكتب «قائمة الحاجات» — يعني يلاقي كل تعريفات var و function ويحجزلها مكان في الذاكرة. بعد كده بييجي يشغّل الكود سطر سطر من فوق لتحت. ده اسمه Hoisting: «رفع» التعريفات لأعلى الـ scope في مرحلة الإنشاء قبل التنفيذ.
التعريف العلمي بالظبط
طبقًا لـ ECMAScript Language Specification (ECMA-262)، JavaScript engine بيتعامل مع كل scope في مرحلتين: مرحلة الإنشاء (Creation Phase) ومرحلة التنفيذ (Execution Phase). في مرحلة الإنشاء، الـ engine بيكوّن Lexical Environment Record ويسجّل فيه كل الـ bindings الموجودة في الـ scope ده.
- تعريفات
varبتترصد بقيمة مبدئيةundefined. - تعريفات
letوconstبترصد كـ uninitialized — وده اللي بيخلق منطقة اسمها Temporal Dead Zone (TDZ). - تعريفات الـ Function Declaration بترفع بكامل جسم الدالة، مش بس الاسم.
كود فعلي يوضّح الفرق
// مع var: بيشتغل، بيطبع undefined
console.log(name); // undefined
var name = "أحمد";
console.log(name); // "أحمد"
// مع let: بيرمي ReferenceError
console.log(age); // ReferenceError: Cannot access 'age' before initialization
let age = 30;