شرح سياق التنفيذ والرفع في JavaScript مع أمثلة عملية
تُعد JavaScript من أكثر لغات البرمجة سهولةً من حيث البدء، لكنها تحتوي على مفاهيم أساسية لا بد من فهمها بعمق إذا كنت ترغب في كتابة شيفرة أوضح، وتصحيح الأخطاء بسرعة، وبناء منطق برمجي متماسك. من أهم هذه المفاهيم: Execution Context وHoisting.
فهم هذين المفهومين لا يساعدك فقط على قراءة الشيفرة بشكل أفضل، بل يمهّد أيضاً لاستيعاب مفاهيم أخرى مثل this وscope وclosure بطريقة أسهل وأكثر دقة.
ما هو سياق التنفيذ Execution Context في JavaScript؟
عند كتابة ملف برمجي بلغة JavaScript، فإنك تنشئ متغيرات ودوال وكائنات ومصفوفات وتعليمات متنوعة. لكن هذه الشيفرة لا تُنفّذ مباشرة كما هي مكتوبة، بل تمر بعدة مراحل داخل المحرك البرمجي حتى تصبح قابلة للتنفيذ.
من المهم هنا التمييز بين مفهومين مترابطين:
Lexical Environment: يصف المكان البنيوي الذي كُتبت فيه العناصر داخل الشيفرة.Execution Context: البيئة الفعلية التي تُنفَّذ فيها الشيفرة لحظة التشغيل.
انظر إلى المثال التالي:
function doSomething() {
var age = 7;
// Some more code
}
في هذا المثال، المتغير age موجود من الناحية التركيبية داخل الدالة doSomething(). هذا التمركز البنيوي هو جزء من Lexical Environment. لكن تنفيذ الشيفرة نفسها لا يحدث إلا عندما ينشأ Execution Context مناسب.

ماذا يحدث داخل سياق التنفيذ؟
حين يبدأ المحرك بتنفيذ الشيفرة، فإنه لا يقرأها قراءة سطحية فقط، بل يمررها عبر مراحل داخلية متتابعة، منها:
- التجزئة أو
Tokenizing: تحويل الشيفرة إلى وحدات صغيرة مفهومة تسمىTokens. - التحليل أو
Parsing: تحويل هذه الوحدات إلى شجرة بنيوية تُسمىASTاختصاراً لـAbstract Syntax Tree. - توليد الشيفرة القابلة للتنفيذ أو
Code Generation: تحويل البنية السابقة إلىbyte-codeيمكن لمحرك اللغة تشغيله، ثم تحسينه لاحقاً عبرJIT.
مثال بسيط على سطر برمجي يمر بهذه المراحل:
var age = 7;

لهذا يمكننا القول إن Execution Context هو الإطار الذي تُدار داخله عملية التهيئة والتنفيذ الفعلي للشيفرة.
أنواع سياق التنفيذ في JavaScript
يوجد نوعان أساسيان من سياق التنفيذ:
Global Execution Contextويُختصر إلىGEC.Function Execution Contextويُختصر إلىFEC.
وكل نوع منهما يمر بمرحلتين رئيسيتين:
- مرحلة الإنشاء أو
Creation Phase. - مرحلة التنفيذ أو
Execution Phase.
سياق التنفيذ العام Global Execution Context
بمجرد تشغيل أي ملف JavaScript، ينشئ المحرك سياق تنفيذ عاماً. هذا السياق هو الأساس الذي تبدأ منه بقية العمليات.
مرحلة الإنشاء في السياق العام
في هذه المرحلة، تحدث عدة أمور مهمّة:
- إنشاء كائن عام يُسمى
windowفي بيئة المتصفح. - إنشاء المتغير الخاص
this. - حجز مساحة في الذاكرة للمتغيرات المعلنة.
- إسناد القيمة الخاصة
undefinedإلى متغيراتvarقبل التنفيذ الفعلي. - تخزين تعريفات الدوال المعلنة بصيغة
function declarationمباشرة في الذاكرة.
مرحلة التنفيذ في السياق العام
في هذه المرحلة يبدأ تنفيذ الأسطر الفعلية، مثل إسناد القيم إلى المتغيرات وتشغيل التعليمات. أما الدوال فلا تُنفذ إلا عند استدعائها.
مثال: تحميل ملف برمجي فارغ
إذا أنشأت ملفاً باسم index.js وتركته فارغاً، ثم ربطته بملف HTML كما يلي:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./index.js"></script>
</head>
<body>
I'm loading an empty script
</body>
</html>
فعند تحميل الصفحة، سيُنشأ Global Execution Context حتى لو كان الملف البرمجي فارغاً. ويمكنك من خلال أدوات المطور في المتصفح كتابة window أو this في وحدة التحكم لملاحظة أنهما يشيران إلى الكائن العام نفسه داخل هذا السياق.



الاستنتاج هنا بسيط: حتى الملف الفارغ يمر بمرحلة إنشاء للسياق العام، وفيها يتم تجهيز window وthis.
مثال: متغيرات ودوال داخل السياق العام
var blog = 'freeCodeCamp';
function logBlog() {
console.log(this.blog);
}
في مرحلة الإنشاء:
- يُنشأ
windowوthis. - تُحجز الذاكرة للمتغير
blog. - يُسند إلى
blogمبدئياً القيمةundefined. - يُخزَّن تعريف الدالة
logBlog()في الذاكرة مباشرة.
وفي مرحلة التنفيذ:
- تُسنَد القيمة
'freeCodeCamp'إلى المتغيرblog. - لا تُنفَّذ الدالة
logBlog()لأنها لم تُستدعَ بعد.
سياق تنفيذ الدالة Function Execution Context
عند استدعاء أي دالة، ينشأ سياق تنفيذ جديد خاص بها. هذا يعني أن كل استدعاء للدالة يملك بيئته التنفيذية المستقلة.
لنوسّع المثال السابق:
var blog = 'freeCodeCamp';
function logBlog() {
console.log(this.blog);
}
logBlog();
بمجرد استدعاء logBlog()، يُنشأ Function Execution Context جديد. هذا السياق يمر أيضاً بمرحلتين: الإنشاء ثم التنفيذ.
ما الذي يميّز سياق تنفيذ الدالة؟
- يوفر الكائن الخاص
argumentsالذي يحتوي على المعاملات المرسلة إلى الدالة. - يملك نطاقاً خاصاً يحدد كيفية الوصول إلى المتغيرات.
- يمكنه الوصول إلى ما هو متاح من السياق الخارجي وفق قواعد
scope.
وإذا استدعت دالة دالةً أخرى، فإن المحرك ينشئ سياق تنفيذ جديداً لكل استدعاء جديد. هذه السلسلة هي ما يفسر كثيراً من سلوك المتغيرات أثناء التشغيل.
ما هو الرفع Hoisting في JavaScript؟
مصطلح Hoisting قد يوحي خطأً بأن المحرك يرفع الشيفرة فعلياً إلى أعلى الملف. لكن ما يحدث في الواقع مختلف: المحرك يجهّز بعض التعريفات في الذاكرة خلال Creation Phase قبل بدء التنفيذ سطراً بسطر.
بمعنى آخر، الرفع ليس نقلاً فعلياً للأسطر، بل نتيجة مباشرة لطريقة إنشاء Execution Context.
رفع المتغيرات Variable Hoisting
تأمل المثال التالي:
console.log(name);
var name;
الناتج سيكون:
undefined
السبب هو أن المتغير name حُجزت له ذاكرة في مرحلة الإنشاء، وأُعطيت له القيمة الابتدائية undefined قبل الوصول إلى سطر console.log(name).
إذن، رفع المتغيرات يعني:
- التصريح باستخدام
varيُجهّز في الذاكرة مبكراً. - لكن الإسناد الفعلي للقيمة لا يحدث إلا في مرحلة التنفيذ.
مثلاً:
name = 'freeCodeCamp';
في هذه الحالة، تُسنَد القيمة أثناء التنفيذ، بعد أن يكون المتغير قد أُنشئ مسبقاً في الذاكرة.
رفع الدوال Function Hoisting
الأمر نفسه ينطبق على الدوال المعلنة باستخدام function declaration. انظر إلى المثال التالي:
functionA();
function functionA() {
console.log('Function A');
functionB();
}
function functionB() {
console.log('Function B');
}
الناتج سيكون:
Function A
Function B
لماذا نجح هذا الاستدعاء رغم أن الدوال كُتبت بعد الاستخدام؟ لأن تعريفات الدوال الكاملة توضع في الذاكرة خلال مرحلة الإنشاء، ثم تُنفَّذ عند استدعائها.
وهنا تظهر فائدة فهم الفرق بين:
function declaration: يتم رفعه كاملاً.function initializationأو الدوال المخزّنة داخل متغير: لا تتصرف بالطريقة نفسها.
قواعد مهمّة لفهم الرفع بشكل صحيح
1) اكتب التصريحات قبل الاستخدام كلما أمكن
رغم أن الرفع قد يسمح أحياناً باستخدام العنصر قبل تعريفه الظاهري، فإن أفضل ممارسة هي إعلان المتغيرات والدوال قبل استعمالها. هذا يجعل الشيفرة أوضح وأسهل في الصيانة.
2) الرفع لا يعني رفع التهيئة
انظر إلى المثال التالي:
logMe();
var logMe = function() {
console.log('Logging...');
};
هذا المثال سيسبب خطأ، لأن logMe هنا متغير تم التصريح عنه باستخدام var، وليس دالة معلنة بصيغة function declaration. لذلك يُرفع المتغير فقط ويُعطى القيمة undefined، ثم يحاول المحرك استدعاء undefined كما لو كانت دالة.

3) التعامل مع let وconst مختلف
تأمل هذا المثال:
console.log(name);
let name;
هنا ستحصل على خطأ من النوع ReferenceError. والسبب أن المتغيرات المعلنة باستخدام let وconst تُرفع من حيث البنية الداخلية، لكنها لا تُهيأ بالقيمة undefined بالطريقة نفسها التي يحدث بها الأمر مع var.

لذلك فإن استخدام let وconst يساعد على تقليل الأخطاء الناتجة عن الوصول غير المقصود إلى متغيرات قبل تهيئتها.
مقارنة سريعة بين var وlet وconst في الرفع
| نوع التصريح | هل يُرفع؟ | القيمة الابتدائية قبل التنفيذ | النتيجة عند الوصول المبكر |
|---|---|---|---|
var |
نعم | undefined |
يعرض undefined |
let |
نعم | لا تُتاح مباشرة | خطأ ReferenceError |
const |
نعم | لا تُتاح مباشرة | خطأ ReferenceError |
function declaration |
نعم | التعريف الكامل للدالة | يمكن استدعاؤها قبل موضعها النصي |
أفضل الممارسات عند التعامل مع سياق التنفيذ والرفع
- احرص على تعريف المتغيرات والدوال قبل استخدامها.
- فضّل استخدام
letوconstعند الحاجة لتقليل السلوكيات المربكة. - لا تعتمد على الرفع كوسيلة تنظيم، بل اعتبره سلوكاً داخلياً يجب فهمه فقط.
- فرّق دائماً بين
function declarationوfunction expression. - عند تصحيح الأخطاء، فكّر في مرحلتَي
Creation PhaseوExecution Phaseبدلاً من النظر إلى الشيفرة كسطور متتابعة فقط.
لماذا يُعد فهم هذه المفاهيم مهماً للمطور؟
إذا كنت تتساءل لماذا تعمل شيفرة معيّنة رغم أن الاستدعاء سبق التعريف، أو لماذا يظهر undefined أحياناً ويظهر ReferenceError أحياناً أخرى، فإن الجواب غالباً يرتبط مباشرة بـ Execution Context وHoisting.
فهم هذين المفهومين يمنحك قدرة أفضل على:
- تحليل سلوك المحرك أثناء التشغيل.
- توقّع الأخطاء قبل وقوعها.
- تحسين أسلوب كتابة الشيفرة.
- استيعاب مفاهيم متقدمة مثل
scope chainوclosureوارتباطthis.
الخلاصة التقنية
Execution Context هو الإطار الذي يحدد كيف تُجهَّز الشيفرة وتُنفَّذ داخل محرك JavaScript، بينما يُعد Hoisting أثراً مباشراً لمرحلة الإنشاء داخل هذا السياق. من الناحية العملية، لا ينبغي الاعتماد على الرفع في تصميم الشيفرة، لكن فهمه ضروري لتفسير سلوك المتغيرات والدوال بدقة. كلما استوعبت هذا الأساس جيداً، أصبحت قراءتك لشيفرة JavaScript أكثر احترافية، وقلت الأخطاء المنطقية التي تظهر أثناء التطوير.