مقدمة
الإغلاق هو وظيفة لديها إذن للوصول إلى المتغيرات في نطاق وظيفة آخر.
من الصعب فهم عمليات الإغلاق في JavaScript. تعتمد العديد من التطبيقات المتقدمة على عمليات الإغلاق لتنفيذها. دعونا أولاً نلقي نظرة على مثال أدناه:
وظيفة OUTER () {var i = 100 ؛ وظيفة inner () {console.log (i) ؛ }}في الكود أعلاه ، وفقًا لنطاق المتغير ، تكون جميع المتغيرات المحلية في الوظيفة الخارجية مرئية للوظيفة الداخلية ؛ المتغيرات المحلية في الوظيفة الداخلية غير مرئية خارج الوظيفة الداخلية ، لذلك لا يمكن قراءة المتغيرات المحلية في الوظيفة الداخلية خارج الوظيفة الداخلية.
نظرًا لأن الوظيفة الداخلية يمكنها قراءة المتغيرات المحلية للوظيفة الخارجية ، طالما يتم استخدام الداخلية كقيمة الإرجاع ، يمكن قراءة المتغيرات المحلية الداخلية مباشرة خارج OUER.
وظيفة OUTER () {var i = 100 ؛ وظيفة inner () {console.log (i) ؛ } الإرجاع الداخلي ؛} var rs = Outer () ؛ rs () ؛هذه الوظيفة لها خصائصان:
بعد تنفيذ VAR RS = OUTER () بهذه الطريقة ، يشير RS الفعلي إلى الوظيفة الداخلية. هذا الرمز هو في الواقع إغلاق. وهذا يعني ، عندما يتم الرجوع إلى الوظيفة الداخلية داخل الوظيفة الخارجية بواسطة متغير خارج الوظيفة الخارجي ، يتم إنشاء إغلاق.
نِطَاق
ببساطة ، النطاق هو النطاق الذي يمكن الوصول إليه من المتغيرات والوظائف ، أي النطاق يتحكم في رؤية وحيرة الحياة للمتغيرات والوظائف. في JavaScript ، يكون نطاق المتغيرات عالميًا ومحليًا.
النطاق العالمي
var num1 = 1 ؛ function fun1 () {num2 = 2 ؛}الكائنات الثلاثة أعلاه num1 و num2 و fun1 كلها نطاقات عالمية. تجدر الإشارة هنا إلى أن المتغيرات التي تحدد المهام المباشرة في النهاية يتم الإعلان عنها تلقائيًا على أنها نطاقات عالمية ؛
النطاق المحلي
Function Wrap () {var obj = "أنا ملفوفة بالالتفاف ، والخارج من التفاف لا يمكن الوصول إلي مباشرة" ؛ وظيفة innerfun () {// الخارج لا يمكن الوصول لي}}سلسلة النطاق
كل شيء في JavaScript هو كائن. تحتوي هذه الكائنات على خاصية [[Scope]] ، والتي تحتوي على مجموعة من الكائنات في النطاق الذي أنشأته الوظيفة. تسمى هذه المجموعة سلسلة نطاق الوظيفة ، والتي تحدد البيانات التي يمكن الوصول إليها بواسطة الوظيفة.
وظيفة إضافة (A ، B) {return a+b ؛}عند إنشاء وظيفة ، ستضيف خاصية [[النطاق]] تلقائيًا النطاق العالمي
var sum = add (3،4) ؛
عندما يتم استدعاء وظيفة ، يتم إنشاء كائن داخلي يسمى سياق التنفيذ. يحدد هذا الكائن Z البيئة عند تنفيذ الوظيفة. كما أن لديها سلسلة نطاقها الخاصة لحل المعرف ، ويتم تهيئة سلسلة نطاقها ككائن موجود في [[النطاق]] لوظيفة التشغيل الحالية.
أثناء تنفيذ الوظيفة ، في كل مرة يتم فيها مواجهة متغير ، سيتم تمرير عملية تحليل المعرف لتحديد مكان الحصول على البيانات وتخزينها. تبدأ هذه العملية من رأس سلسلة النطاق ، أي يبحث عن معرف يحمل نفس الاسم من الكائن النشط. إذا تم العثور عليه ، استخدم المتغير المقابل لهذا المعرف. إذا لم يتم العثور عليها ، فاستمر في البحث عن الكائن التالي في سلسلة النطاق ، إذا تم البحث في جميع الكائنات (آخر شيء هو كائن عالمي) ، فإن المعرف يعتبر غير محدد.
إنهاء
الإغلاق هو ببساطة وظيفة تصل إلى متغيراتها الخارجية.
var quo = function (status) {return {getStatus: function () {return status ؛ }}}يتم حفظ الحالة في Quo ، وهي تُرجع كائنًا ، وتشير طريقة getStatus في هذا الكائن إلى متغير الحالة ، أي أن وظيفة getStatus تصل إلى حالتها المتغيرة الخارجية ؛
var newvalue = quo ('string') ؛ // إرجاع كائن مجهول ، يشار إليه بواسطة newValue مع newValue.getStatus () ؛ // الوصول إلى الحالة المتغيرة الداخلية لـ Quoإذا لم تكن طريقة getStatus متوفرة ، فسيتم إعادة تدوير الحالة تلقائيًا بعد Quo ('Sting'). وذلك على وجه التحديد لأن الكائن المجهول الذي تم إرجاعه يتم الرجوع إليه بواسطة كائن عالمي ، ويعتمد الكائن المجهول على الحالة ، لذلك سيمنع إطلاق الحالة.
مثال:
// مخطط الخطأ var test = function (noles) {var i ؛ لـ (i = 0 ؛ i <nodes.length ؛ i ++) {noldes [i] .onclick = function (e) {Alert (i) ؛ }}}تعمل وظيفة مجهولة على إنشاء إغلاق ، والوصول إلى I IT هي I في وظيفة الاختبار الخارجية ، لذلك تشير كل عقدة فعليًا إلى نفس i.
// تحسين حل var test = function (noles) {var i ؛ لـ (i = 0 ؛ i <nodees.length ؛ i ++) {noles [i] .onclick = function (i) {return function () {Alert (i) ؛ } ؛ }(أنا)؛ }}كل عقدة مرتبطة بحدث. يتلقى هذا الحدث معلمة ويعمل على الفور ، ويمر في i. نظرًا لأنه يتم تمريره بالقيمة ، ستقوم كل حلقة بإنشاء نسخة احتياطية جديدة للتيار الأول.
دور الإغلاق
وظيفة OUTER () {var i = 100 ؛ وظيفة inner () {console.log (i ++) ؛ } الإرجاع الداخلي ؛} var rs = Outer () ؛ rs () ؛ // 100rs () ؛ // 101rs () ؛ // 102في الكود أعلاه ، RS هي الوظيفة الداخلية الإغلاق. ركضت RS ثلاث مرات في المجموع ، والمرة الأولى كانت 100 ، والمرة الثانية كانت 101 ، والمرة الثالثة هي 102. وهذا يدل على أن المتغير المحلي I في الوظيفة الخارجي قد تم الاحتفاظ به في الذاكرة ولم يتم مسحه تلقائيًا عند استدعاء.
الغرض من الإغلاق هو أنه بعد الانتهاء من التنفيذ الخارجي وإعادته ، فإن الإغلاق يجعل آلية جمع القمامة في JavaScript (مجموعة Grabage) لا تدوير الذاكرة التي يشغلها الخارجي ، لأن تنفيذ الوظيفة الداخلية للمتغيرات الخارجية يعتمد على المتغيرات الخارجية. (تفسير آخر: الخارجي هو الوظيفة الأصل للداخلية ، يتم تعيين الداخلية إلى متغير عالمي ، مما يتسبب في أن يكون داخليًا في الذاكرة طوال الوقت ، ويعتمد وجود داخلي على الخارجي ، لأن بعض الخارجي دائمًا في الذاكرة ولن يتم جمع القمامة وإعادة تدويرها بعد انتهاء المكالمة).
الإغلاق لديه إذن للوصول إلى جميع المتغيرات داخل الوظيفة.
عندما تُرجع الدالة إغلاقًا ، سيتم حفظ نطاق الوظيفة في الذاكرة حتى لا يكون الإغلاق موجودًا.
الإغلاق والمتغيرات
نظرًا لآلية سلسلة النطاق ، يمكن أن يحصل الإغلاق على القيمة الأخيرة فقط التي تحتوي على أي متغير في الوظيفة. انظر المثال التالي:
الدالة f () {var rs = [] ؛ لـ (var i = 0 ؛ i <10 ؛ i ++) {rs [i] = function () {return i ؛ } ؛ } return rs ؛} var fn = f () ؛ for (var i = 0 ؛ i <fn.length ؛ i ++) {console.log ('function fn [' + i + '] () قيمة الإرجاع:' + fn [i] ()) ؛}الوظائف ستعيد صفيف. على السطح ، يبدو أن كل وظيفة يجب أن تعيد قيمة الفهرس الخاصة بها. في الواقع ، تُرجع كل وظيفة 10. وذلك لأن سلسلة نطاق الوظيفة الأولى تحتوي على الكائنات النشطة للوظيفة f ، وتشير إلى نفس المتغير i. عند إرجاع الوظيفة F ، فإن قيمة المتغير I هي 10. في هذا الوقت ، تقوم كل وظيفة بحفظ نفس كائن المتغير للمتغير i. يمكننا فرض الإغلاق على التصرف كما هو متوقع من خلال إنشاء وظيفة مجهولة أخرى.
الدالة f () {var rs = [] ؛ لـ (var i = 0 ؛ i <10 ؛ i ++) {rs [i] = function (num) {return function () {return num ؛ } ؛ }(أنا)؛ } return rs ؛} var fn = f () ؛ for (var i = 0 ؛ i <fn.length ؛ i ++) {console.log ('function fn [' + i + '] () قيمة الإرجاع:' + fn [i] ()) ؛}في هذا الإصدار ، بدلاً من تعيين الإغلاق إلى الصفيف مباشرة ، نحدد وظيفة مجهولة المصدر ونخصص نتيجة تنفيذ الوظيفة المجهولة على الفور إلى الصفيف. هنا ، وظيفة مجهولة لديها معلمة num. عند استدعاء كل وظيفة ، نمر في المتغير i. نظرًا لأن المعلمات يتم تمريرها حسب القيمة ، سيتم نسخ المتغير الذي يتم نسخه إلى المعلمة NUM. داخل هذه الوظيفة المجهولة ، يتم إنشاء وإغلاق NUM وإعادته. وبهذه الطريقة ، تحتوي كل وظيفة في صفيف RS على نسخة من متغير NUM الخاص بها ، لذلك يمكن إرجاع قيم مختلفة.
هذا الكائن في الإغلاق
var name = 'jack' ؛ var o = {name: 'bingdian' ، getName: function () {return function () {return this.name ؛ } ؛ }} console.log (o.getName () () ()) ؛ // jackvar name = 'jack' ؛ var o = {name: 'bingdian' ، getName: function () {var self = this ؛ إرجاع وظيفة () {return self.name ؛ } ؛ }} console.log (o.getName () () ()) ؛ // بينغديانتسرب الذاكرة
دالة issignHandler () {var el = document.getElementById ('Demo') ؛ el.onclick = function () {console.log (el.id) ؛ }} exmentHandler () ؛ينشئ الرمز أعلاه إغلاقًا كمعالج حدث EL Element ، وهذا الإغلاق يخلق مرجعًا دائريًا. طالما وجود الوظيفة المجهولة ، فإن عدد المراجع EL هو على الأقل 1 ، لأن الذاكرة التي تشغلها لن يتم إعادة تدويرها أبدًا.
دالة issignHandler () {var el = document.getElementById ('Demo') ؛ var id = el.id ؛ el.onclick = function () {console.log (id) ؛ } el = null ؛} issignHandler () ؛يمكن أن يؤدي تعيين EL NULL إلى إشراف كائن DOM والتأكد من أنه يستهلك الذاكرة بشكل طبيعي.
تقليد نطاق مستوى الكتلة
ينتمي البيان المحدد في أي زوج من الأقواس المجعد ({و}) إلى كتلة ، وجميع المتغيرات المحددة في هذا غير مرئية خارج كتلة الكود ، والتي نسميها نطاق مستوى الكتلة.
(function () {// block-level scope}) () ؛تطبيق الإغلاق
حماية أمن المتغيرات في الوظيفة. كما في المثال السابق ، يمكن للوظيفة الداخلية فقط الوصول إلى الوظيفة الخارجي ، ولكن لا يمكن الوصول إليها من خلال قنوات أخرى ، وبالتالي حماية أمان i.
الحفاظ على متغير في الذاكرة. كما في المثال السابق ، بسبب الإغلاق ، فإن I في الوظيفة الخارجي موجود دائمًا في الذاكرة ، لذلك في كل مرة يتم تنفيذ Rs () ، سيتم إضافتي 1.