in

ما هو سياق التنفيذ 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.

[better-ads type=”banner” banner=”13615″ campaign=”none” count=”2″ columns=”1″ orderby=”rand” order=”ASC” align=”center” show-caption=”0″][/better-ads]

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

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

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

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

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

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

https://gist.github.com/YassineBajdou/fee90c3e811b5679b9aa213e9f5f466e

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

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

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

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

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

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

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

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

 

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

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

 

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

https://gist.github.com/YassineBajdou/7c0e00e422595759d4e57947fc23d85d

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

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

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

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

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

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

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

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

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

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

https://gist.github.com/YassineBajdou/4bb435cb26236b9ff5f53501a76d49af

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

https://gist.github.com/YassineBajdou/e4514bd5c60ee1e6d27ec3515b14fcc4

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

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

https://gist.github.com/YassineBajdou/37f1e5a5607ccfabc2b776ada1ffb2c6

الرفع Hoisting

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

https://gist.github.com/YassineBajdou/6640181d1d2aa9ea60e9043e087dcb54

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

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

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

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

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

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

اقرأ أيضا: 

  • إعداد: علي صقر علي.
  • مراجعة: نور عبدو.

بواسطة علي صقر علي

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

ما سر مناعة الفيلة ضد السرطان؟

أي من هذه الأعمال الفنية ليس من صُنع الإنسان؟