تعد عمليات الإغلاق ميزة مهمة في JavaScript ، وأكبر وظائفها هي حفظ المعلومات أثناء تشغيل الوظيفة. في JavaScript ، يتم اشتقاق العديد من ميزات الإغلاق من سلسلة النطاق أثناء مكالمات الوظائف.
سلسلة نطاق كائنات ومتغيرات استدعاء الوظائف
لكل مكالمة دالة في JavaScript ، ستقوم JavaScript بإنشاء كائن محلي لتخزين المتغيرات المحلية المحددة في الوظيفة ؛ إذا كانت هناك وظيفة متداخلة داخل الوظيفة ، فسيحدد JavaScript كائنًا محليًا متداخلًا على الكائن المحلي المحدد بالفعل. بالنسبة للوظيفة ، هناك العديد من طبقات تعريفات الوظائف المتداخلة كما توجد ، مثل العديد من طبقات الأشياء المحلية المتداخلة. يسمى هذا الكائن المحلي "كائن استدعاء الوظيفة" ("كائن الاتصال" في ECMASCRIPT 3 ، وتم إعادة تسميته "سجل البيئة التعريفي" في ECMASCRIPT 5 ، لكنني شخصياً أعتقد أن الاسم في ECMASCRIPT 3 أسهل في الفهم). يتم استخدام مكالمات الوظائف التالية كمثال:
نسخة الكود كما يلي:
وظيفة f (x) {
var a = 10 ؛
إرجاع A*X ؛
}
console.log (F (6)) ؛ // 60
في هذا المثال البسيط ، عندما يتم استدعاء وظيفة F () ، ستقوم JavaScript بإنشاء كائن استدعاء لوظيفة F () (دعنا نسميها f_invokeobj). هناك خصائصان داخل كائن f_invokeobj: A و X ؛ عند تشغيل F () ، تكون القيمة 10 وقيمة X هي 6 ، وبالتالي فإن نتيجة الإرجاع النهائية هي 60. التوضيح هو كما يلي:
عندما يكون هناك تداخل وظيفة ، ستقوم JavaScript بإنشاء كائنات استدعاء وظائف متعددة:
نسخة الكود كما يلي:
وظيفة f (x) {
var a = 10 ؛
إرجاع A*g (x) ؛
وظيفة G (ب) {
العودة ب*ب ؛
}
}
console.log (F (6)) ؛ // 360
في هذا المثال ، عند استدعاء وظيفة F () ، ستقوم JavaScript بإنشاء كائن استدعاء لوظيفة F () (F_InvokeObj) ، والتي لها سمتان A و X ، والقيمة A هي 10 والقيمة X هي 6 ؛ عند تشغيل f () ، ستقوم JavaScript بتحليل وظيفة G () في الدالة F () وإنشاء كائن مكالمة من G () (G_InvokeObj) ، والذي يحتوي على سمة B ، والقيمة B هي نفسها المعلمة التي تم تمريرها X ، وبالتالي فإن نتيجة الإرجاع النهائية هي 360.
كما ترون ، يشكل كائن استدعاء الوظيفة سلسلة. عند تشغيل الوظيفة المدمجة G () وتحتاج إلى الحصول على القيمة المتغيرة ، ستبدأ في البحث من كائن استدعاء الوظيفة الأحدث. إذا كان لا يمكن البحث فيه ، فالبحث في كائن مكالمة إضافي على طول سلسلة كائنات استدعاء الوظيفة ، وهو ما يسمى "سلسلة نطاق المتغيرات". إذا ظهر المتغير نفسه في كائنين استدعاء الوظيفة ، فستأخذ الوظيفة القيمة المتغيرة في كائن الاتصال الأقرب إليه:
نسخة الكود كما يلي:
وظيفة f (x) {
var a = 10 ؛
إرجاع A*g (x) ؛
وظيفة G (ب) {
var a = 1 ؛
العودة b*b*a ؛
}
}
console.log (F (6)) ؛ // 360 ، وليس 3600
في المثال أعلاه ، يحتوي المتغير A على قيم مختلفة في كائن الاتصال (G_InvokeObj) لوظيفة G () وكائن الاتصال (F_INVOKEOBJ) لوظيفة F (). عند تشغيل وظيفة G () ، تكون قيمة A المستخدم داخل وظيفة G () 1 ، في حين أن قيمة المستخدم خارج وظيفة G () هي 1. سلسلة كائن استدعاء الوظيفة في هذا الوقت هي كما يلي:
ما هو الإغلاق؟
جميع الوظائف في javaScript هي كائنات ، وعند تحديد الوظائف ، سيتم إنشاء سلسلة مكالمات من الدالة المقابلة. تعريف الوظيفة يتوافق مع سلسلة من كائنات استدعاء الوظائف. طالما وجود كائن الوظيفة ، يوجد كائن استدعاء الوظيفة المقابل ؛ بمجرد عدم استخدام الوظيفة ، سيتم جمع كائن استدعاء الوظيفة المقابلة ؛ ويسمى هذا المزيج من كائن الوظيفة وسلسلة كائن استدعاء الوظيفة "الإغلاق". في الأمثلة المذكورة أعلاه لوظيفة F () ودالة G () ، هناك إغلاقان: كائن دالة F () وكائن F_INVOKEOBJ يشكلون إغلاقًا ، وكائن دالة G () وسلسلة كائن G_INVOKEOBJ-F_INVOKEOBJ تشكل الإغلاق الثاني. عند تنفيذ وظيفة G () ، نظرًا لأن وظيفة G () لم تعد تستخدم ، يتم جمع الإغلاق G () ؛ ثم ، عند تنفيذ وظيفة F () ، يتم جمع إغلاق F () أيضًا القمامة لنفس السبب.
من تعريف الإغلاق ، يمكننا استنتاج استنتاج: جميع وظائف JavaScript هي إغلاق بعد التعريف لأن جميع الوظائف هي كائنات ، وجميع الوظائف لها أيضًا سلاسل كائن المكالمات المقابلة لها بعد التنفيذ.
ومع ذلك ، فإن ما يجعل الإغلاق يعمل حقًا هو حالة الوظائف المتداخلة. نظرًا لأن الوظيفة المدمجة يتم تعريفها فقط عند تشغيل الوظيفة الخارجية ، فإن القيمة المتغيرة المخزنة في إغلاق الوظيفة المضمنة (خاصة القيمة المتغيرة المحلية للدالة الخارجية) هي القيمة أثناء التشغيل. طالما لا يزال كائن الوظيفة المدمجة موجودًا ، فإن إغلاقه لا يزال موجودًا (لا تتغير القيمة المتغيرة في الإغلاق) ، وبالتالي تحقيق الغرض من حفظ معلومات عملية تشغيل الوظيفة. النظر في المثال التالي:
نسخة الكود كما يلي:
var a = "خارج" ؛
وظيفة f () {
var a = "Inside" ؛
الدالة g () {return a ؛}
إرجاع ز ؛
}
var result = f () ؛
console.log (result ()) ؛ // Inside
في هذا المثال ، عند تشغيل وظيفة F () ، يتم تعريف وظيفة G () ، ويتم إنشاء إغلاق وظيفة G (). يحتوي الإغلاق G () على سلسلة كائن G_INVOKEOBJ-F_INVOKEOBJ ، وبالتالي يتم حفظ قيمة المتغير A أثناء تنفيذ دالة F (). عند تنفيذ عبارة console.log () ، لا يزال إغلاق G () موجودًا لأن كائن وظيفة G لا يزال موجودًا ؛ عند تشغيل كائن وظيفة G الذي لا يزال قيد التنفيذ ، ستستخدم JavaScript الإغلاق G () الذي لا يزال قيد الاستمرار ويحصل على قيمة المتغير A ("داخل") منه.