سأحاول في هذه المقالة تحليل آلية تنفيذ عمليات الإغلاق في جافا سكريبت بالتفصيل. لهذا ، سأستخدم متصفح 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 مكونات:
- بيئة السجل - سجل بيئة يسمح لك بتحديد العلاقة بين المعرفات وما هو متاح لنا في سياق المكالمة الحالية
- رابط خارجي جنيه. كل وظيفة لها خاصية [[نطاقات] داخلية عندما يتم إنشائها .
متغير البيئة - في معظم الأحيان هو نفس جنيه. الفرق بين الاثنين هو أن قيمة 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.
وداعا :) ونراكم قريبا. أعجبني واشترك في قناتي :)