ما أنت ، يغلق في جافا سكريبت؟

سأحاول في هذه المقالة تحليل آلية تنفيذ عمليات الإغلاق في جافا سكريبت بالتفصيل. لهذا ، سأستخدم متصفح Chrome.

لنبدأ بالتعريف:
الإغلاقات هي وظائف تشير إلى متغيرات مستقلة (مجانية). بمعنى آخر ، الوظيفة المحددة في الإغلاق "تذكر" البيئة التي تم إنشاؤها فيها.
MDN

إذا كان هناك شيء غير واضح بالنسبة لك في هذا التعريف ، فهذا ليس مخيفًا. مجرد قراءة على.

أنا مقتنع تمامًا أن فهم شيء ما أسهل وأسرع بأمثلة محددة.

لذلك ، أقترح أخذ قطعة من التعليمات البرمجية والمشي بها مع المترجم الفوري من البداية إلى النهاية بخطوات وتصنيف ما يحدث.

لذلك دعونا نبدأ:


الشكل 1

نحن في السياق العالمي للدعوة ، إنه عالمي (ويعرف أيضًا باسم Window في المتصفح) ونرى أن الوظيفة الرئيسية تكمن بالفعل في السياق الحالي وجاهزة للعمل.


الشكل 2

يحدث هذا لأن كل إعلان الوظيفة (يشار إليه فيما يلي باسم FD) يرتفع دائمًا في أي سياق ، ويتم تهيئته على الفور وجاهز للعمل. يحدث الشيء نفسه مع المتغيرات المعلنة من خلال var ، يتم تهيئة قيمها فقط على أنها غير محددة.

من المهم أيضًا أن نفهم أن جافا سكريبت أيضًا "تثير" المتغيرات المعلنة عبر let و const. الفرق الوحيد هو أنه لا يقوم بتهيئتها كـ var أو FD. لذلك ، عندما نحاول الوصول إليها قبل التهيئة ، نحصل على خطأ مرجعي.

أيضًا ، بشكل عام ، نرى خاصية مخفية داخليًا [[النطاقات]] - هذه قائمة من السياقات الخارجية التي يمكن الوصول إليها من الرئيسي. في حالتنا ، Global موجودة ، حيث يتم إطلاق main في سياق عالمي.

تشير حقيقة أن تهيئة المراجع إلى البيئة الخارجية في JavaScript تحدث في وقت إنشاء الوظيفة ، وليس في وقت التنفيذ ، إلى أن JS هي لغة ذات نطاق ثابت. وهذا مهم.

المضي قدما:


الشكل 3

نذهب إلى الوظيفة الرئيسية وأول ما يلفت انتباهك هو الكائن المحلي (في المواصفات - localEnv). هناك نرى ، حيث يتم الإعلان عن هذا المتغير عبر var و "برزت" ، حسناً ، وبالتقليد نرى جميع FDs 3 (foo ، bar ، baz). الآن دعونا معرفة من أين جاء كل شيء.

عند بدء تشغيل أي سياق ، يتم تشغيل العملية المجردة NewDeclarativeEnvironment ، مما يسمح بتهيئة LexicalEnvironment (المشار إليها فيما يلي بـ LE) و VariableEnvironment . أيضًا ، تأخذ NewDeclarativeEnvironment حجة واحدة - LE الخارجية ، لإنشاء [[نطاقات]] التي تحدثنا عنها أعلاه. LE هو واجهة برمجة التطبيقات التي تتيح لنا تحديد العلاقة بين المعرفات والمتغيرات الفردية والوظائف. يتكون جنيه من 2 مكونات:

  1. بيئة السجل - سجل بيئة يسمح لك بتحديد العلاقة بين المعرفات وما هو متاح لنا في سياق المكالمة الحالية
  2. رابط خارجي جنيه. كل وظيفة لها خاصية [[نطاقات] داخلية عندما يتم إنشائها .

متغير البيئة - في معظم الأحيان هو نفس جنيه. الفرق بين الاثنين هو أن قيمة VariableEnvironment لا تتغير أبدًا ، ويمكن أن تتغير LE أثناء تنفيذ التعليمات البرمجية. لتبسيط مزيد من الفهم ، أقترح دمج هذه المكونات في واحد - جنيه.

يوجد أيضًا في Local Local هذا بسبب حقيقة أن ThisBinding كان يسمى - وهذا أيضًا طريقة مجردة تهيئة هذا في السياق الحالي.

بالطبع ، تلقى كل FD على الفور [[النطاقات]]:


الشكل 4

نرى أن جميع FDs وردت في [[Scopes]] مجموعة من [Closure main، Global] ، وهو أمر منطقي.

في الشكل أيضًا ، نرى Call Stack - إنها بنية بيانات تعمل وفقًا لمبدأ LIFO - آخرها أولاً. نظرًا لأن JavaScript مترابط واحدًا ، يمكن تنفيذ سياق واحد فقط في كل مرة. في حالتنا ، هذا هو سياق الوظيفة الرئيسية. كل استدعاء دالة جديد ينشئ سياقًا جديدًا ، مكدسة.

في الجزء العلوي من الرصة يكون دائمًا سياق التنفيذ الحالي. بعد اكتمال الدالة تنفيذها وتنفيذ المترجم الفوري ، تتم إزالة سياق المكالمة من المكدس. هذا كل ما نحتاج إلى معرفته حول Call Stack في هذه المقالة :)

نلخص ما حدث في السياق الحالي:

  • في وقت الإنشاء ، تلقى الرئيسي [[النطاقات]] مع روابط للبيئة الخارجية
  • دخل المترجم جسم الوظيفة الرئيسية
  • استدعاء المكدس حصلت على سياق التنفيذ الرئيسي
  • هذا تهيئة
  • تهيئة جنيه

في الواقع ، الجزء الأصعب قد انتهى. ننتقل إلى الخطوة التالية في الكود:

الآن نحن بحاجة إلى استدعاء باز للحصول على النتيجة.


الشكل 5

تمت إضافة سياق استدعاء baz جديد إلى Call Stack. نرى أن كائن إغلاق جديد قد ظهر. هنا نحصل على ما هو متاح لنا من [[النطاقات]]. لذلك وصلنا إلى هذه النقطة. هذا هو الختام. كما ترون في الشكل 4 ، يأتي الإغلاق (الرئيسي) أولاً في قائمة سياقات "النسخ الاحتياطي" في الباز. مرة أخرى لا السحر.

دعنا ندعو فو:


الشكل 6

من المهم أن تعرف أنه بغض النظر عن المكان الذي نسميه foo ، فسوف تتبع دائمًا المعرفات غير المحددة في سلسلة [[النطاقات]]. وهي ، في الرئيسي ، ثم في العالمية ، إن لم تكن موجودة في الرئيسي.

بعد تنفيذ foo ، عادت القيمة ، وسار سياقها من Call Stack.
نمر إلى الدعوة إلى شريط وظيفة. في سياق تنفيذ الشريط ، يوجد متغير يحمل نفس اسم المتغير في LE foo - a . لكن ، كما خمنت بالفعل ، هذا لا يؤثر على أي شيء. سوف foo لا يزال يأخذ القيمة من [[النطاقات]].
مكان الدعوة لا يؤثر على النطاق ، فقط مكان الخلق
logachyova


الشكل 7

نتيجة لذلك ، سيعود baz 300 وسيُطرد من Call Stack. بعد ذلك سيحدث نفس الشيء مع السياق الرئيسي ، حيث تنتهي جزء الشفرة من التنفيذ.

نحن نلخص:

  • أثناء إنشاء الوظيفة ، يتم ضبط [[النطاقات]] . هذا مهم جدًا لفهم عمليات الإغلاق ، لأن المترجم الفوري يتبع هذه الروابط فورًا عند البحث عن القيم
  • ثم ، عند استدعاء هذه الوظيفة ، يتم إنشاء سياق تنفيذ نشط ، والذي يتم وضعه في Call Stack
  • يتم تنفيذ ThisBinding ويتم تعيين هذا للسياق الحالي
  • تتم تهيئة LE ، وتتاح كل وسيطات الدوال ومتغيرات الإعلان من خلال var و FD. علاوة على ذلك ، إذا كانت هناك متغيرات تم الإعلان عنها عبر let أو const ، فسيتم إضافتها أيضًا إلى LE
  • إذا لم يجد المترجم أي معرف في السياق الحالي ، فسيتم استخدام [[النطاقات]] لمزيد من البحث ، والتي يتم فرزها جميعًا بدورها. إذا تم العثور على القيمة ، فإن الرابط الخاص بها يقع في كائن Closure الخاص. في نفس الوقت ، لكل سياق يغلق به السياق الحالي ، يتم إنشاء إغلاق منفصل مع المتغيرات الضرورية
  • إذا لم يتم العثور على القيمة في أي نطاقات ، بما في ذلك Global ، يتم إرجاع ReferenceError.

هذا كل شئ!

آمل أن يكون هذا المقال مفيدًا لك وأن تفهم الآن كيف تعمل آلية التأمين في JavaScript.

وداعا :) ونراكم قريبا. أعجبني واشترك في قناتي :)

Source: https://habr.com/ru/post/ar459166/


All Articles