التجديد:
الإغلاق هو صعوبة في لغة JavaScript وميزةها. تعتمد العديد من التطبيقات المتقدمة على عمليات الإغلاق للتنفيذ.
ميزات الإغلاق
الإغلاق لها ثلاث خصائص:
1. الوظائف المتداخلة
2. يمكن أن تشير الوظيفة إلى المعلمات والمتغيرات الخارجية في الداخل
3. لن يتم جمع المعلمات والمتغيرات بواسطة آلية جمع القمامة
تعريف الإغلاق ومزاياه وعيوبه
تشير عمليات الإغلاق إلى الوظائف التي لديها إمكانية الوصول إلى المتغيرات في نطاق وظيفة أخرى. الطريقة الأكثر شيوعًا لإنشاء عمليات الإغلاق هي إنشاء وظيفة أخرى داخل وظيفة واحدة والوصول إلى المتغيرات المحلية لهذه الوظيفة من خلال وظيفة أخرى.
عيب الإغلاق هو أنها ذاكرة مقيمة ، مما سيزيد من استخدام الذاكرة ، ويمكن أن يؤدي الاستخدام غير السليم بسهولة إلى تسرب الذاكرة.
الإغلاق هي ميزة رئيسية للغة JavaScript. التطبيق الرئيسي للإغلاق هو بشكل أساسي: تصميم الأساليب والمتغيرات الخاصة.
بعد تنفيذ الوظيفة العامة ، يتم تدمير الكائن النشط المحلي ويتم حفظ النطاق العالمي فقط في الذاكرة. لكن وضع الإغلاق مختلف!
للحديث عن الموضوع
1. تعريف الإغلاق؟
دعونا نلقي نظرة على بعض التعريفات حول الإغلاق:
1. يشير الإغلاق إلى وظيفة لديها إذن للوصول إلى المتغيرات في نطاق وظيفة آخر.
2. يمكن ربط كائنات الوظيفة من خلال سلاسل النطاق ، ويمكن حفظ المتغيرات الموجودة داخل جسم الوظيفة في نطاق الوظيفة. هذه الخاصية تسمى "الإغلاق".
3. يمكن للوظائف الداخلية الوصول إلى المعلمات ومتغيرات الوظائف الخارجية التي تحددها (باستثناء هذا والوسائط).
إذا كنت ترغب في تعلم مفهوم إغلاق JS بشكل منهجي ، فيمكنك الرجوع إلى عمود JS E-Book لموقع Wulin.com للتعلم.
دعنا نلخص التعريف
1. يمكنك الوصول إلى وظائف المتغيرات في نطاق الوظيفة الخارجي
2. يمكن حفظ متغيرات الوظائف الخارجية التي يمكن الوصول إليها بواسطة الوظائف الداخلية في نطاق الوظيفة الخارجية دون إعادة تدويرها-وهذا هو القلب. في وقت لاحق ، عندما نواجه إغلاق ، يجب أن نفكر في الأمر. يجب أن نركز على المتغير المشار إليه بواسطة الإغلاق.
إنشاء إغلاق بسيط
var saynname = function () {var name = 'jozo' ؛ return function () {Alert (name) ؛}} ؛ var say = sayname () ؛ يقول()؛دعونا نفسر الجملتين التاليتين:
• var says = sayname (): إرجاع وظيفة داخلية مجهولة المخزنة في المتغير يقول ويشير إلى الاسم المتغير للوظيفة الخارجية. بسبب آلية جمع القمامة ، بعد تنفيذ وظيفة SayName ، لم يتم تدمير الاسم المتغير.
• قل (): قم بتنفيذ الوظيفة الداخلية التي تم إرجاعها ، ولا يزال الوصول إلى الاسم المتغير والإخراج "Jozo".
2. سلسلة النطاق في الإغلاق
فهم سلاسل النطاق مفيد أيضًا لفهم الإغلاق.
يجب أن تكون طرق البحث في المتغيرات في النطاق مألوفة للغاية ، ولكن في الواقع ، هذا هو ما يبحث صعودًا على طول سلسلة النطاق.
عندما تسمى الوظيفة:
1. أولاً إنشاء سياق تنفيذ وسلسلة النطاق المقابل ؛
2. إضافة وسيطات وقيم المعلمات الأخرى المسماة إلى الكائن النشط للدالة (كائن التنشيط)
سلسلة النطاق: يحتوي الكائن النشط للوظيفة الحالية على أولوية أعلى ، يليه الكائن النشط للوظيفة الخارجية ، والكائن النشط للدالة الخارجية للوظيفة الخارجية يتناقص بالتسلسل حتى نهاية سلسلة النطاق - النطاق العالمي. الأولوية هي ترتيب عمليات البحث المتغيرة ؛
دعونا نلقي نظرة أولاً على سلسلة النطاق العادي:
الدالة sayname (name) {return name ؛} var says = sayname ('jozo') ؛يحتوي هذا الرمز على نطاقين: أ. النطاق العالمي B.SayName نطاق وظيفة ، أي أن هناك كائنين متغيران فقط. عند تنفيذها على بيئة التنفيذ المقابلة ، سيصبح الكائن المتغير كائنًا نشطًا وسيتم دفعه إلى الطرف الأمامي لسلسلة نطاق بيئة التنفيذ ، أي أنه أعلى أولوية. تحدث إلى الصورة:
هذه الصورة متوفرة أيضًا في كتب البرمجة المتقدمة JS ، وقد أعدت ترتيبها جميعًا.
عند إنشاء وظيفة sayname () ، يتم إنشاء سلسلة نطاق تحتوي على الكائن المتغير مقدمًا ، أي سلسلة النطاق التي تفهرستها 1 في الشكل ، ويتم حفظها في السمة الداخلية [[النطاق]]. عند استدعاء وظيفة sayname () ، يتم إنشاء بيئة تنفيذ ، ثم يتم بناء سلسلة النطاق عن طريق نسخ الكائن في سمة [[النطاق]] للوظيفة. بعد ذلك ، يتم إنشاء كائن نشط آخر (مفهرس بواسطة 0 في الشكل) ودفعه إلى الطرف الأمامي لسلسلة نطاق بيئة التنفيذ.
بشكل عام ، عند تنفيذ الوظيفة ، سيتم تدمير الكائن النشط المحلي ويتم حفظ النطاق العالمي فقط في الذاكرة. ومع ذلك ، فإن وضع الإغلاق مختلف:
دعونا نلقي نظرة على سلسلة الإغلاق النطاق:
الدالة sayname (name) {return function () {return name ؛}} var says = sayname ('jozo') ؛يحتوي مثيل الإغلاق هذا على نطاق آخر لوظيفة مجهولة من المثال السابق:
بعد إرجاع الوظيفة المجهولة من دالة SayName () ، تتم تهيئة سلسلة نطاقها إلى كائن نشط وكائن متغير عالمي يحتوي على وظيفة sayname (). وبهذه الطريقة ، يمكن للدالة المجهولة الوصول إلى جميع المتغيرات والمعلمات المحددة في SayName (). الأهم من ذلك ، بعد تنفيذ وظيفة sayname () ، لن يتم تدمير كائنها النشط ، لأن سلسلة نطاق الوظيفة المجهولة لا تزال تشير إلى الكائن النشط. وبعبارة أخرى ، بعد تنفيذ وظيفة SayName () ، سيتم تدمير سلسلة نطاق بيئة التنفيذ الخاصة بها ، ولكن سيتم ترك كائنها النشط في الذاكرة ، مع العلم أنه سيتم تدمير الوظيفة المجهولة. هذه هي أيضًا مشكلة تسرب الذاكرة التي ستتم مناقشتها لاحقًا.
أنا لا أكتب كثيرًا عن مشكلات سلسلة النطاق ، وكتابة الأشياء في الكتاب هي أيضًا متعبة للغاية يا (□)
3. مثال الإغلاق
مثال 1: تنفيذ التراكم
// method 1var a = 0 ؛ var add = function () {a ++ ؛ console.log (a)} add () ؛ add () ؛ // method 2: closure var add = (function () {var a = 0 ؛ return function () {a ++ ؛ console.log (a) ؛}}) () ؛ console.log (a) ؛ // undefinedadd () ؛ add () ؛وبالمقارنة ، فإن الطريقة 2 أكثر أناقة ، وتقلل أيضًا من المتغيرات العالمية ، وخصخصة المتغيرات.
مثال 2: أضف حدث انقر فوق كل لي
var oli = document.getElementsByTagName ('li') ؛ var i ؛ for (i = 0 ؛ i <5 ؛ i ++) {oli [i] .onclick = function () {Alert (i) ؛}} console.log (i) ؛ // 5 // تنفيذ دالة مجهولة المصدر (function () {Alert (i) ؛ // 5} ()) ؛ما سبق مثال كلاسيكي. نعلم جميعًا أن نتيجة التنفيذ هي أن 5 منبثقة ، ونعلم أيضًا أنه يمكن استخدام عمليات الإغلاق لحل هذه المشكلة ، لكن في البداية ، ما زلت لا أستطيع أن أفهم سبب ظهور 5 منبثقة ولماذا يمكن أن تحل هذه المشكلة. في وقت لاحق ، قمت بفرزها وتوضح ذلك:
أ. دعنا أولاً نحلل الموقف قبل استخدام الإغلاق: في الحلقة ، نربط دالة مجهولة الهوية لكل حدث انقر فوق لي ، وقيمة المتغير الذي أعود إليه في الدالة المجهولة. عندما تنتهي الحلقة ، تصبح قيمة المتغير I 5. في هذا الوقت ، ننقر على كل li ، أي تنفيذ الوظيفة المجهولة المقابلة (انظر الرمز أعلاه). هذا هو المتغير I بالفعل 5 ، لذا فإن كل نقرة تنبثق 5. نظرًا لأن كل وظيفة مجهولة التي يتم إرجاعها هنا تشير إلى نفس المتغير I ، إذا أنشأنا متغيرًا جديدًا لحفظ القيمة التي أجريتها عند تنفيذ الحلقة ، فدع الوظيفة المجهولة تنطبق على هذا المتغير ، وأخيراً إرجاع هذه الوظيفة المجهولة ، بحيث يمكن تحقيق هدفنا. يتم تحقيق ذلك باستخدام عمليات الإغلاق!
ب. دعنا نحلل الموقف عند استخدام الإغلاق:
var oli = document.getElementsByTagName ('li') ؛ var i ؛ for (i = 0 ؛ i <5 ؛ i ++) {oli [i] .onclick = (function (num) {var a = num ؛ // لتوضيح وظيفة إرجاع المشكلة () {ALERT (a) ؛}}) (i)}}}}} // 5عند تنفيذ الحلقة for ، يتم تمرير الوظيفة المجهولة المرتبطة بحدث النقر I وتنفيذها على الفور لإرجاع وظيفة مجهولة داخلية. نظرًا لأن المعلمات يتم تمريرها حسب القيمة ، فإن المعلمة الرسمية NUM تحفظ القيمة الحالية لـ I ، ثم تقوم بتعيين القيمة للمتغير المحلي A. ثم تحافظ الدالة الداخلية المجهولة على مرجع A ، أي ، تحافظ على القيمة الحالية لـ i. لذلك بعد تنفيذ الحلقة ، انقر فوق كل LI ، وستظهر الوظيفة المجهولة التي تم إرجاعها قيمة المحفوظ A.
4. تطبيق الإغلاق
دعونا نلقي نظرة على الغرض من الإغلاق. في الواقع ، باستخدام عمليات الإغلاق ، يمكننا القيام بالكثير من الأشياء. على سبيل المثال ، محاكاة نمط الكود الموجهة للكائنات ؛ الكود التعبير أكثر أناقة وإيجازًا ؛ وتحسين كفاءة تنفيذ الكود في بعض الجوانب.
1. وظيفة التنفيذ الذاتي المجهولة
في المواقف الفعلية ، غالبًا ما نواجه موقفًا لا يلزم تنفيذ فيه بعض الوظائف إلا مرة واحدة ولا تحتاج متغيراتها الداخلية إلى الحفاظ عليها ، مثل تهيئة واجهة المستخدم ، حتى نتمكن من استخدام الإغلاق:
// قم بتغيير جميع خطوط li إلى Red (function () {var els = document.getElementSbyTagName ('li') ؛ لـ (var i = 0 ، lng = els.length ؛ i <lng ؛ i ++) {els [i] .style.color = 'red' ؛}}) () ؛نقوم بإنشاء وظيفة مجهولة وتنفيذها على الفور. نظرًا لأن الخارجي لا يمكن أن يشير إلى المتغيرات الموجودة بداخله ، فسيتم إصدار المتغيرات المحلية مثل ELS و I و LNG بعد التنفيذ بفترة وجيزة ، مما يوفر الذاكرة!
المفتاح هو أن هذه الآلية لن تلوث الكائن العالمي.
2. تنفيذ رمز التغليف/المعيار
var person = function () {// يكون نطاق المتغير داخل الوظيفة ، ولا يمكن الوصول إلى name = "الافتراضي" في الخارج. إرجاع {getName: function () {return name ؛ } ، setName: function (newName) {name = newName ؛ }}} () ؛ console.log (person.name) ؛ // الوصول المباشر ، والنتيجة هي console.log غير محددة (person.getName ()) ؛ // default person.setName ("Jozo") ؛ console.log (person.getName ()) ؛ // Jozo3. تنفيذ الكائن الموجهة
وبهذه الطريقة ، فإن الكائنات المختلفة (مثيلات الفصول) لديها أعضاء وحالات مستقلة ولا تتداخل مع بعضها البعض. على الرغم من عدم وجود آلية مثل الطبقة في JavaScript ، باستخدام عمليات الإغلاق ، يمكننا محاكاة مثل هذه الآليات. دعنا نتحدث عن المثال أعلاه:
وظيفة person () {var name = "default" ؛ إرجاع {getName: function () {return name ؛ } ، setName: function (newName) {name = newName ؛ }}} ؛ var person1 = person () ؛ print (person1.getName ()) ؛ John.setName ("person1") ؛ print (person1.getName ()) ؛ // person1 var person2 = person () ؛ print (person2.getName ()) ؛ Jack.SetName ("eerson2") ؛ print (eSon2.GetName ()) ؛ // person2الشخصان الشخصان الشخصان 1 و person2 لا يتدخلان مع بعضهما البعض! لأن هاتين الحالتين لديهما وصول مستقل إلى عضو الاسم.
5. تسرب الذاكرة والحلول
آلية إعادة تدوير القمامة
بالحديث عن إدارة الذاكرة ، فإنه لا ينفصل بشكل طبيعي عن آلية جمع القمامة في JS. هناك استراتيجيتان لتحقيق جمع القمامة: مارك التخليص والعد المرجعية ؛
إزالة العلامة: عندما يعمل جامع القمامة ، فإنه سيحدد جميع المتغيرات المخزنة في الذاكرة. بعد ذلك ، سيقوم بإزالة علامات المتغيرات في البيئة وعلامات المتغيرات المشار إليها من قبل المتغيرات في البيئة. بعد ذلك ، إذا تم تمييز المتغير مرة أخرى ، فهذا يعني أن المتغير جاهز للحذف. حتى عام 2008 ، استخدمت كل من Firefox و Opera و Chrome و Safari's JavaScript هذه الطريقة ؛
عدد المرجع: تتبع عدد مرات الرجوع إلى كل قيمة. عندما يتم الإعلان عن المتغير ويتم تعيين قيمة لنوع مرجع للمتغير ، فإن عدد المرات التي تتم الإشارة إلى هذه القيمة هي 1. إذا تم تعيين هذه القيمة إلى متغير آخر ، يتم زيادة عدد المرات التي يتم فيها زيادة المرجع بمقدار 1. وعلى العكس من ذلك ، فإن عدد المتغيرات ينحرف عن مرجع القيمة.
مشكلة كبيرة في هذه الطريقة هي مرجع دائري ، أي الكائن A يحتوي على مؤشر إلى B ، والكائن B يحتوي أيضًا على إشارة إلى A. يمكن أن يتسبب ذلك في إعادة تدوير قدر كبير من الذاكرة (تسرب الذاكرة) ، لأن مراجعها لا يمكن أن تكون 0. كان أحد الأسباب وراء تسبب الإغلاق في تسرب الذاكرة كان عيبًا في هذه الخوارزمية.
نحن نعلم أن بعض الأشياء في IE ليست كائنات JavaScript الأصلية. على سبيل المثال ، يتم تنفيذ الكائنات في BOM و DOM في شكل كائنات com ، وآلية جمع القمامة لكائنات com تعتمد العد المرجعي. لذلك ، على الرغم من أن محرك JavaScript الخاص بـ IE يعتمد استراتيجية لتطهير العلامات ، إلا أن الوصول إلى كائنات COM لا يزال يعتمد على العد المرجعي ، طالما تم تصميم كائنات COM في IE ، ستكون هناك مشكلة في المراجع الدائرية!
خذ كستناء:
window.onload = function () {var el = document.getElementById ("id") ؛ el.onclick = function () {Alert (el.id) ؛}}لماذا يسبب هذا الرمز تسرب الذاكرة؟
el.onclick = function () {Alert (el.id) ؛} ؛عند تنفيذ هذا الرمز ، يتم تعيين كائن الوظيفة المجهول إلى سمة onclick من EL ؛ ثم تشير الوظيفة المجهولة إلى كائن EL في الداخل ، وهناك مرجع دائري ، لذلك لا يمكن إعادة تدويره ؛
حل:
window.onload = function () {var el = document.getElementById ("id") ؛ var id = el.id ؛ // عدم المرجع el.onclick = function () {Alert (id) ؛ } el = null ؛ // امسح الكائن النشط في الوظيفة الخارجية المشار إليها بواسطة الإغلاق}ما ورد أعلاه هو ملخص المعرفة ذات الصلة حول سلسلة إغلاق سلسلة مجموعة القمامة التي قدمها لك المحرر. آمل أن يكون ذلك مفيدًا للجميع!