ما هو سياق التنفيذ Execution Context والمُكدِّس Stack في الجافا سكريبت؟

سنتحدّث في هذا المقال بعمقٍ عن أحد المفاهيم الأساسية في لغة جافا سكريبت JavaScript، وهو مفهوم سياق التنفيذ (Execution Context).  كما سيتضح لك، في نهاية المقال، ما الذي يحاول مُفسِّرُ جافا سكريبت القيامَ به، والسبب الكامن وراء إمكانية استعمال بعض المتحولات (variables) والتوابع (الدّوالّ Functions) قبل التصريح عنها، وكيفية حساب قيمتها.

ما هو سياق التنفيذ؟

عندما يتم تنفيذ كود ما في جافا سكريبت، فإن البيئة التي تُحيط بالتنفيذ مهمة جدًا، وبناءً عليها يُعتبرُ الكود واحدًا من ضمن الخيارات التالية:

  • كود عام (General Code)؛ هنا تكون البيئة المحيطة هي البيئة العامّة؛ حيث يبدأ تنفيذ أي برنامج مكتوب بلغة جافا سكريبت.
  • كود يُنفذ داخل تابع (Function Code)؛ هنا تكون البيئة هي عبارة عن كل ما يستطيع التابع الوصول إليه من متحولات وتوابع أخرى وكائنات (Objects) عامة.
  • كود ينفذ داخل التابع المضمن في لغة جافا سكريبت، وهو التابع eval.

تشيرُ العديدُ من المصادرِ المُتاحة عبر الإنترنت إلى سياق التنفيذ بالكلمة مجال (Scope)، وللسهولة سنعتبر صيغة سياق التنفيذ هي المجال أو البيئة التي يتم تقييم البرنامج وفقها؛ مثلًا، بفرض احتوى البرنامج متحول اسمه myVariable فإنّ تقييم هذا المتحول يكون حسب بيئة التنفيذ.

إليك بمثال عن كود ضمن سياق عام وكود ضمن سياق تابع:

لدينا في هذا المثال سياق عام واحد، محاط بالإطار ذي اللون الارجواني. يوجد في أي برنامج جافا سكريبت دومًا سياق تنفيذ عام واحد فقط، ويُمكن لجميع السياقات الأخرى أن تصل إليه؛ إي أنّه داخل تابع ما نستطيع الوصول لمتحول معرّف ضمن السياق العام. وكما في المثال أعلاه، كل من التوابع person وfirstName وlastName  تستطيع الوصول إلى المتحول sayHello.

أمّا بالنسبة لسياق التابع فمن الممكن أن يتعدّد؛ كما في المثال لدينا سياق التابع person المحاط بالإطار الأخضر، وسياق التابع firstName المحاط بالإطار الأزرق، وسياق التابع lastName المحاط بالإطار البرتقالي. كل تابع يشكل السياق الخاص به، ولا يستطيع أي سياق خارج التابع الوصول بشكل مباشر إليه. وفي مثالنا، لا يستطيع السياق العام الوصولَ إلى المتحولات first وlast، المُعرّفة ضمن التابع person.

مُكدِّس سياق التنفيذ Execution Context Stack

إنّ مُفسِّر جافا سكريبت، الذي يعمل ضمن المتصفحات، عبارةٌ عن نيسب (Thread) واحد فقط؛ بمعنى أنّه ينفذ عملية واحدة فقط في الوقت نفسه، لا يستطيع تنفيذ تابعين معًا على سبيل المثال، ومن هنا تأتي أهمية المُكدِّس.

عندما يقوم المتصفح بتحميل ملف جافا سكريبت، فإنه يدخل بشكل مباشر في سياق التنفيذ العام كما هو موضح في الصورة السابقة: سياق التنفيذ العام هو في قاع المُكدِّس؛ أي أول سياق بدأ المُفسِّر بتنفيذه. وفي حال استدعى السياق العام تابع ما، يقوم المُفسِّر بإنشاء سياق تنفيذ خاص بهذا التابع ووضعه داخل المُكدِّس (push)، ليبدأ بتنفيذه.

في حال تم استدعاء تابع آخر ضمن التابع الحالي، تتكرر العملية؛ حيث يتم إيقاف العمل بالسياق الحالي، وإنشاء سياق للتابع الذي تم استدعاؤه، ويتم وضعه في قمة المُكدِّس والعمل عليه. يعمل المُفسِّر دومًا على السياق الموجود في قمة المُكدِّس، وعندما ينتهي منه، يزيله من المُكدِّس ليعود التحكم إلى السياق الذي يسبقه.

ولتوضيح المفاهيم السابقة، سنطرح المثال التالي:

ليكن لدينا التابع التالي:

يستدعي هذا التابع نفسه 3 مرّات مع زيادة قيمة المُوسِط (parameter) i بمقدار (1) كل مرة يُستدعى فيها التابع foo، يتم إنشاء سياق جديد ووضعه ضمن المُكدِّس، وعند انتهاء التنفيذ تتم إزالته كما هو موضّح في الصورة.

الآن، قبل الانتقال إلى شرح سياق التنفيذ بالتفصيل، هنالك خمسة مفاتيح أساسية عليك تذكّرها عن مُكدِّس السياق:

  1. نيسب وحيد.
  2. تنفيذ متزامن.
  3. سياق عام واحد.
  4. سياقات توابع عديدة.
  5. كل استدعاء لتابع يؤدي إلى إنشاء سياق تنفيذ جديد، ووضعه في قمة المُكدِّس.

سياق التنفيذ بالتفصيل

تنقسم عملية إنشاء سياق تنفيذ إلى مرحلتين أساسيتين:

  1. مرحلة الإنشاء:

يتم فيها ما يلي:

  • تشكيل سلسلة السياق (Scope Chain)، والكائن أو الغرض المتغيّر.
  • التصريح عن المتحولات والمُوسِطات والتوابع.
  • حساب قيمة المتحول this.

 

  1. مرحلة تنفيذ البرنامج:

يتم فيها إسناد القيم إلى المتحولات، ويبدأ المُفسِّر بالتنفيذ.

 

يمكن التعبير عن سياق التنفيذ على شكل كائن له 3 خصائص كما يلي:

الكائن المتغير (Variable Object VO)، والكائن المعبّر عن سياق التنفيذ (executionContextObj)، الذي يتم إنشاؤه عند استدعاء التابع، ولكن قبل تنفيذه؛ أي في المرحلة الأولى “مرحلة الإنشاء”، حيث يقوم المُفسِّر بمسح التابع، ووضع كافة المتحولات والمُوسِطات والتعريفات المحلية للتوابع والمتحولات ضمن ما يعرف بالكائن المتغير ضمن الكائن المعبر عن سياق التنفيذ، كما توضح الصورة أعلاه.

مخطط بسيط عن كيفية تنفيذ المُفسِّر للتوابع

  1. مصادفة كود ما يقوم باستدعاء تابع.
  2. يتم إنشاء سياق التنفيذ قبل تنفيذ التابع.
  3. الدخول في مرحلة الإنشاء.
  • إنشاء سلسلة السياق (Scope Chain).
  • إنشاء الغرض المتغير (Variable Object).
  • إنشاء غرض المُوسِطات؛ وهو غرض يحوي تعريف كل مُوسِط وقيمته.
  • البحث ضمن السياق عن تعريف توابع محلية.

–    عند إيجاد أي تابع ضمن السياق يتم تخزين اسمه وعنوانه ضمن الذاكرة في الغرض المتغير.

–    في حال كان الاسم موجود ضمن الكائن المتغير يتم إسناد العنوان الجديد له.

  • البحث ضمن السياق عن تعريف متحولات محلية.

–    عند إيجاد متحول محلّي يتم تخزين اسمه في الغرض المتغير وإسناد القيمة (undefined) له.

–    في حال كان المتحول موجود مسبقًا لا يقوم المُفسِّر بأي عمل.

  • حساب قيمة المتحول this.
  1. الدخول في مرحلة التنفيذ.
  • تنفيذ الكود سطر تلو الآخر، وإسناد القيم الحقيقية للمتحولات.

لدينا المثال التالي للتوضيح:

عند استدعاء foo(22)، تكون مرحلة الإنشاء كما يلي:

كما يتّضح، في مرحلة الإنشاء، يتم تخزين أسماء المتحولات، وإسناد القيمة undefined لهم باستثناء المُوسِطات، مثلًا المُوسِط i يملك القيمة 22، وتخزين عناوين التوابع في الذاكرة.

بعد انتهاء مرحلة الإنشاء تبدأ مرحلة التنفيذ، وتصبح حالة الكائن أو الغرض المعبر عن السياق على الشكل التالي:

الرفع Hoisting

تُشير بعض المصادر إلى مفهوم الرفع، بإمكانيّة استخدام متحولات وتوابع قبل التصريح عنها؛ أي رفعها دون الدخول في التفاصيل. وحسب ما ورد من معلوماتٍ في هذا المقال، فإنّ السبب في ذلك هو مرحلة الإنشاء، التي يتم فيها التصريح عن كل المتحولات والتوابع، في السياق التنفيذي، ضمن ما يسمى بالكائن أو الغرض المتغير. كما في المثال:

في المثال أعلاه، لماذا يمكننا استخدام التابع foo قبل التصريح عنه؟

بسبب تخزين اسم التابع وعنوانه في مرحلة الإنشاء ضمن الكائن المتغير.

لماذا نمط foo هو تابع وليس متحول؟

كما لاحظت، في المخطط الذي أوردناه سابقًا، أن التوابع يتم تخزينها قبل المتحولات في مرحلة الإنشاء، إذًا، يتم تخزين التابع foo قبل المتحول foo، وبالتالي يمر المُفسِّر على تعريف المتحول foo دون أن يقوم بشيء؛ لأنّ اسمه موجود مسبقًا ضمن الكائن المتغير.

لماذا قيمة bar هي undefined؟

لأنه تم استخدامه قبل أن يصل التنفيذ إلى مرحلة إسناد قيمة له.

اقرأ أيضا: 

مشروعنا غير ربحي، ومُموّل ذاتيًا، نحن لا نتلقى أي أموال حكومية أو من أي جهة كانت سياسية أو غيرها، كما أنّنا لا نلتمس ذلك. و بالإضافة للتمويل الذاتي، الذي يبلغ حاليا 99٪ من مجمل التمويل، نحن نعتمد على المساهمة الطوعية لمؤسسات خاصة وأفراد مثلك لتطوير المشروع وتحقيق أهدافه. لدعمنا إضغط هنا

  • إعداد: علي صقر علي
  • مراجعة: نور عبدو
مصدر What is the Execution Context & Stack in JavaScript?
تعليقات
Loading...

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Accept Read More