المستوى: مبتدئ
لو كتبت console.log(x) في أول سطر، وبعدها بسطرين كتبت var x = 5، الكود مش بيطرح خطأ. بيطبع undefined. ده مش bug في المتصفح ولا في Node.js. ده سلوك اسمه Hoisting، وفهمه بيوفّر عليك ساعات من الـ debugging مش هتعرف سببها لو ميعرفتوش.
Hoisting في JavaScript: السلوك اللي بيخلّي الكود يشتغل قبل ما يتعرّف
الكود اللي بيلخبط أي مبتدئ
console.log(name); // undefined (مش error!)
var name = "أحمد";
console.log(name); // "أحمد"
أول طبعة طلعت undefined رغم إن المتغيّر name ما اتعرّفش لسه في السطر اللي قبلها. لو جرّبت نفس الكود مع let بدل var، هتلاقي JavaScript بترميلك ReferenceError. الفرق مش عشوائي، ده ناتج عن خطوة بتعملها JavaScript قبل ما تشغّل أي سطر فعليًا.
المثال البسيط: دفتر الفهرس قبل ما تكتب فيه
تخيّل إنك بتشتري دفتر جديد. قبل ما تبدأ تكتب فيه أي حاجة، الدفتر بيكون فيه أول صفحة فاضية مكتوب عليها "الفهرس". الفهرس موجود من بداية الدفتر، حتى لو لسه ما كتبتش فيه ولا اسم. لو حد سألك: "في حاجة في الفهرس؟"، هتقول: "آه، الصفحة موجودة، بس فاضية".
JavaScript بتعمل نفس الحاجة قبل ما تشغّل الكود بتاعك. بتمر على الكود مرة بسرعة، وكل ما تلاقي تعريف var x، بتسجّل في "الفهرس" بتاعها إن في متغيّر اسمه x موجود، بس بقيمة undefined. لما الكود يشتغل فعليًا ويوصل لسطر x = 5، بتكتب القيمة الحقيقية. ده اللي بيخلّي console.log(x) قبل التعريف يطبع undefined بدل ما يكسر. الصفحة موجودة، بس فاضية.
التعريف العلمي الدقيق
JavaScript engine زي V8 (الموجود في Chrome و Node.js 22) و SpiderMonkey (في Firefox) بيشغّل أي scope في مرحلتين:
- Creation Phase: الـ engine بيمر على الكود بدون ما ينفّذ أي حاجة، وبيبني Variable Environment داخل Execution Context. بيحجز مكان في الذاكرة لكل تعريف
varويديله القيمة الابتدائيةundefined. تعريفاتfunction declarationبترفع كاملة بجسمها، يعني الجسم نفسه بيبقى متاح من بداية الـ scope. - Execution Phase: الـ engine بيشغّل السطور واحد ورا التاني، وبيغيّر القيم في الذاكرة بناءً على عمليات التعيين
=.
الـ Hoisting . ده تبسيط بيتقال للمبتدئين عشان يتذكروا السلوك. الحقيقة الدقيقة إن المتغيّر "موجود" في الذاكرة قبل ما تتنفّذ أي سطر، لكن بقيمة فاضية لحد ما يوصل سطر التعيين. ده اللي ECMAScript Specification بيسميه .