⬆️ ما هو Hoisting (الرفع) في JavaScript؟ السر وراء استخدام المتغيرات قبل تعريفها! ⬆️
تخيل أنك تريد استخدام شيء ما قبل أن تحضره من المخزن. في معظم لغات البرمجة، هذا سيتسبب في خطأ. ولكن في JavaScript، قد يبدو الأمر وكأنه يعمل أحياناً! هذا السلوك الغريب يُعرف باسم Hoisting أو "الرفع".
ببساطة، الرفع (Hoisting) هو آلية في JavaScript ترفع (تضع في الذاكرة) إعلانات المتغيرات والوظائف إلى أعلى نطاقها (Scope) قبل تنفيذ الكود. هذا يعني أنه يمكنك، من الناحية النظرية، الرجوع إلى متغير أو دالة قبل أن تُعلن عنها في الكود. ولكن انتبه، القصة ليست بهذه البساطة، فهناك تفاصيل مهمة تختلف بين var و let/const والدوال.
🔍 كيف يعمل الرفع مع المتغيرات المُعلنة بـ var؟
لنبدأ بالنوع التقليدي من المتغيرات: var. عند رفع إعلان متغير var، يتم تحضير مكان له في الذاكرة وقيمته الافتراضية تكون undefined.
مثال توضيحي:
console.log(myName); // ماذا ستكون النتيجة؟
var myName = "أحمد";
console.log(myName); // ماذا ستكون النتيجة هنا؟
النتيجة في وحدة التحكم:
undefined
أحمد
💡 ما الذي حدث؟
- قبل التنفيذ، رفع JavaScript إعلان المتغير
myNameإلى أعلى النطاق وأعطاه القيمة الأوليةundefined. - عند تنفيذ السطر الأول
console.log(myName)، وجد المتغير، ولكن قيمته لا تزالundefined. - ثم يتم تنفيذ عملية التعيين
myName = "أحمد"، فيصبح للمتغير القيمة "أحمد". - السطر الثاني
console.log(myName)يطبع القيمة الحالية "أحمد".
بمعنى آخر، الكود الذي كتبناه يُعامل كما لو كان مكتوباً هكذا من قبل JavaScript:
var myName; // الإعلان مُرفوع، والقيمة undefined
console.log(myName); // undefined
myName = "أحمد"; // التعيين يبقى في مكانه
console.log(myName); // "أحمد"
⚠️ الفخ الشائع: الرفع لا يعني تهيئة القيمة
هذا هو المفهوم الأهم! JavaScript ترفع الإعلان (Declaration) وليس التعيين (Assignment). المتغير يصبح معروفاً، لكنه يبقى undefined حتى يصل الكود المنفذ إلى سطر التعيين.
مثال على الخطأ الشائع:
console.log(age); // undefined
var age = 25; // التعيين هنا
// الكود أعلاه لا يساوي هذا:
console.log(age); // ❌ هذا سيعطي خطأ ReferenceError إذا لم يكن age معلوماً
age = 25;
🚫 ماذا عن let و const؟ هنا يأتي دور "المنطقة الميتة الزمنية" (TDZ)
مع إدخال let و const في ES6، تغيرت قواعد اللعبة لجعل اللغة أكثر أماناً. المتغيرات المُعلنة بـ let و const يتم رفعها أيضاً، لكن لا يمكن الوصول إليها قبل السطر الذي تم الإعلان عنها فيه.
الفترة ما بين بداية النطاق (حيث تم رفع المتغير) ونقطة الإعلان الفعلية تسمى المنطقة الميتة الزمنية (Temporal Dead Zone - TDZ). إذا حاولت الوصول للمتغير داخل هذه المنطقة، ستحصل على خطأ.
مثال مع let:
console.log(city); // ❌ ReferenceError: Cannot access 'city' before initialization
let city = "الرياض";
مثال مع const:
console.log(PI); // ❌ ReferenceError: Cannot access 'PI' before initialization
const PI = 3.14;
🔄 المقارنة في جدول:
| الميزة | var |
let / const |
|---|---|---|
| هل يتم الرفع؟ | نعم | نعم |
| القيمة الافتراضية عند الرفع | undefined |
لا يوجد وصول (TDZ) |
| الوصول قبل الإعلان | ممكن (نتيجة: undefined) |
❌ غير ممكن (خطأ ReferenceError) |
⬆️ الرفع مع الدوال (Functions)
الرفع يعمل بشكل مختلف وأكثر فائدة مع إعلانات الدوال (Function Declarations). هنا، لا يُرفع الإعلان فقط، بل الوظيفة كاملةً، مما يعني أنه يمكنك استدعاء الدالة قبل تعريفها في الكود.
مثال مع دالة عادية (Function Declaration):
// نستدعي الدالة قبل تعريفها!
greet("مريم"); // ✅ "مرحباً، مريم!"
function greet(name) {
console.log(`مرحباً، ${name}!`);
}
هذا الكود يعمل بشكل مثالي لأن تعريف الدالة greet كله تم رفعه إلى الأعلى.
⚠️ استثناء مهم: الدوال المُعبَّر عنها (Function Expressions)
إذا عرّفت دالة باستخدام const أو let (أي كـ Function Expression)، فإنها تتبع قاعدة المتغير التي تحملها. فقط المتغير (مثل sayHello) هو الذي يتم رفعه، وليس تعريف الدالة بداخله.
مثال مع تعبير دالة مُخزن في const:
console.log(sayHello); // ❌ ReferenceError (داخل TDZ)
sayHello(); // ❌ حتى هذه ستفشل
const sayHello = function() {
console.log("Hello!");
};
مثال مع تعبير دالة مُخزن في var (سلوك غريب):
console.log(myFunc); // undefined (لأن var مُرفوعة)
myFunc(); // ❌ TypeError: myFunc is not a function
var myFunc = function() {
console.log("أنا تعبير دالة");
};
هنا، myFunc مُرفوعة كـ var، لذا console.log تطبع undefined. عندما نحاول استدعاء undefined كدالة (myFunc())، نحصل على خطأ نوع مختلف (TypeError).
✅ أفضل الممارسات لتجنب مشاكل الرفع
- اعلن المتغيرات في أعلى النطاق: سواء باستخدام
letأوconst، عرف متغيراتك في بداية الكتلة (Block) أو الدالة. هذا يجعل الكود أكثر وضوحاً. - استخدم
constكلما أمكن، ثمlet: تجنب استخدامvarفي الكود الجديد إلا لسبب قوي. استخدامletوconstيمنع الأخطاء الناتجة عن الرفع. - اعلن الدوال قبل استخدامها: على الرغم من أن إعلانات الدوال تُرفع كاملة، إلا أن تعريفها قبل الاستدعاء يجعل الكود أكثر تنظيماً وسهولة في القراءة.
🧪 مثال عملي ختامي
لنرى كل ما تعلمناه في مثال واحد:
// مثال يوضح الفروقات
console.log(numberVar); // undefined
console.log(numberLet); // ❌ ReferenceError
// console.log(numberConst); // ❌ (لو فككنا التعليق) ReferenceError
var numberVar = 10;
let numberLet = 20;
const numberConst = 30;
sayDeclaration(); // ✅ "أنا دالة مُعلنة"
// sayExpression(); // ❌ ReferenceError (لو فككنا التعليق)
// Function Declaration
function sayDeclaration() {
console.log("أنا دالة مُعلنة");
}
// Function Expression (مع const)
const sayExpression = function() {
console.log("أنا تعبير دالة");
};
🎓 اختبر نفسك
التعليقات
شاركنا رأيك أو أسئلتك حول هذا المقال