نِطَاق
النطاق هو نطاق وظيفة المتغير والوظيفة. جميع المتغيرات المعلنة في وظيفة في JavaScript مرئية دائمًا في جسم الوظيفة. هناك نطاقات عالمية ونطاقات محلية في JavaScript ، ولكن لا يوجد نطاق على مستوى الكتلة. أولوية المتغيرات المحلية أعلى من تلك المتغيرات العالمية. من خلال العديد من الأمثلة ، يمكننا أن نفهم "القواعد غير المعلنة" للنطاق في JavaScript (هذه هي أيضًا أسئلة يتم طرحها غالبًا في المقابلات الأمامية).
1. إعلان متغير مقدمًا
مثال 1:
var scope = "global" ؛ function scopetest () {console.log (scope) ؛ var scope = "local"} scopetest () ؛ // غير محددالإخراج هنا غير محدد ولا يوجد خطأ. وذلك لأن التصريحات الواردة في الوظيفة التي ذكرناها أعلاه مرئية دائمًا في جسم الوظيفة. الوظيفة أعلاه تعادل:
var scope = "global" ؛ function scopetest () {var scope ؛ console.log (النطاق) ؛ Scope = "local"} scopetest () ؛ //محليلاحظ أنه إذا تم نسيان VAR ، يتم الإعلان عن المتغير كمتغير عالمي.
2. لا يوجد نطاق على مستوى الكتلة
على عكس اللغات الأخرى التي نستخدمها بشكل شائع ، لا يوجد نطاق على مستوى الكتلة في JavaScript:
دالة scopetest () {var scope = {} ؛ if (scope extryof object) {var j = 1 ؛ لـ (var i = 0 ؛ i <10 ؛ i ++) {//console.log(i) ؛ } console.log (i) ؛ // الإخراج 10} console.log (j) ؛ // output 1}في JavaScript ، يكون نطاق وظيفة المتغيرات على مستوى الوظيفة ، أي أن جميع المتغيرات في الوظيفة محددة في الوظيفة بأكملها ، والتي تجلب أيضًا بعض "القواعد غير المعلنة" التي سنواجهها إذا لم نكن حريصين:
var scope = "hello" ؛ function scopetest () {console.log (scope) ؛ // ① var scope = "no" ؛ console.log (Scope) ؛ // ②}كان إخراج القيمة في ① غير محدد بالفعل ، وهو أمر مجنون. لقد حددنا قيمة المتغير العالمي. ألا ينبغي أن يكون هذا المكان مرحبا؟ في الواقع ، فإن الكود أعلاه يعادل:
var scope = "hello" ؛ function scopetest () {var scope ؛ console.log (scope) ؛ // ① scope = "no" ؛ console.log (Scope) ؛ // ②}إعلان المتغيرات المبكرة والعالمية لها أولوية أقل من المتغيرات المحلية. وفقًا لهذه القاعدتين ، ليس من الصعب فهم سبب عدم تحديد الناتج.
سلسلة النطاق
في JavaScript ، كل وظيفة لها سياق تنفيذ خاص بها. عندما يتم تنفيذ الكود في هذه البيئة ، سيتم إنشاء سلسلة نطاق من الكائنات المتغيرة. سلسلة النطاق هي قائمة كائن أو سلسلة كائن ، والتي تضمن الوصول المنظم إلى كائنات متغيرة.
الواجهة الأمامية لسلسلة النطاق هي الكائن المتغير لبيئة تنفيذ التعليمات البرمجية الحالية ، والتي تسمى غالبًا "كائن نشط". يبدأ البحث عن المتغيرات من كائن السلسلة الأولى. إذا كان الكائن يحتوي على سمات متغيرة ، فسيتم إيقاف البحث. إذا لم يكن الأمر كذلك ، فسيستمر البحث في البحث عن سلسلة النطاق المتفوقة حتى يتم العثور على الكائن العالمي:
سيؤثر البحث عن سلاسل النطاق خطوة بخطوة أيضًا على أداء البرنامج. كلما طالت سلسلة نطاق المتغيرات ، زاد التأثير على الأداء. هذا أيضًا سبب رئيسي يجعلنا نحاول تجنب استخدام المتغيرات العالمية.
إنهاء
المفاهيم الأساسية
النطاق هو شرط أساسي لفهم الإغلاق. تشير عمليات الإغلاق إلى القدرة على الوصول إلى المتغيرات في النطاق الخارجي ضمن النطاق الحالي.
وظيفة createClosure () {var name = "jack" ؛ return {setstr: function () {name = "rose" ؛ } ، getstr: function () {return name + ": hello" ؛ }}} var builder = new createClosure () ؛ builder.setstr () ؛ console.log (builder.getstr ()) ؛ // روز: مرحبايعيد المثال أعلاه إغلاقين في الوظيفة ، وكلاهما يحافظ على الإشارات إلى النطاق الخارجي ، لذلك يمكن الوصول إلى المتغيرات في الوظيفة الخارجية دائمًا أينما تم استدعاؤها. ستضيف الوظائف المحددة داخل الوظيفة الكائن النشط للدالة الخارجية إلى سلسلة النطاق الخاصة بها. لذلك ، في المثال أعلاه ، يمكن للوظيفة الداخلية الوصول إلى خصائص الوظيفة الخارجية من خلال الوظيفة الداخلية. هذه أيضًا طريقة لجافا سكريبت لمحاكاة المتغيرات الخاصة.
ملاحظة: نظرًا لأن عمليات الإغلاق سيكون لها نطاقات إضافية من الوظائف (تحمل الوظائف المجهولة الداخلية نطاقات من الوظائف الخارجية) ، فإن الإغلاق سوف تشغل مساحة ذاكرة أكثر من وظائف أخرى ، وقد يؤدي الاستخدام المفرط إلى زيادة استخدام الذاكرة.
المتغيرات في الإغلاق
عند استخدام عمليات الإغلاق ، نظرًا لتأثير آلية سلسلة النطاق ، يمكن للإغلاق الحصول على القيمة الأخيرة فقط للوظيفة الداخلية. أحد الآثار الجانبية لذلك هو أنه إذا كانت الوظيفة الداخلية في حلقة ، فإن قيمة المتغير هي دائمًا القيمة الأخيرة.
// هذه الحالة ليست معقولة ولها عوامل تأخير معينة. هذا هو توضيح المشكلات في وظيفة حلقة الإغلاق TimManage () {for (var i = 0 ؛ i <5 ؛ i ++) {setTimeOut (function () {console.log (i) ؛} ، 1000)} ؛ }لا يقوم البرنامج أعلاه بإدخال أرقام 1-5 كما توقعنا ، ولكن يخرج 5 مرات جميعها. دعونا نلقي نظرة على مثال آخر:
الدالة createClosure () {var result = [] ؛ لـ (var i = 0 ؛ i <5 ؛ i ++) {result [i] = function () {return i ؛ }} نتيجة الإرجاع ؛}استدعاء CreateClosure () [0] () إرجاع 5 ، و CreateClosure () [4] () لا تزال قيمة الإرجاع 5. من الأمثلة المذكورة أعلاه ، يمكننا أن نرى المشكلة التي توجد بها الإغلاق عند استخدام وظائف داخلية مع حلقات: لأن سلسلة النطاق لكل وظيفة نشطة للوظائف الخارجية (Timemanage ، الإبداع) ، فإنهم يشيرون إلى نفس الشيء. عند إرجاع الوظيفة الخارجية ، تكون قيمة I في هذا الوقت 5 ، وبالتالي فإن قيمة كل وظيفة داخلية I هي أيضًا 5.
فكيف تحل هذه المشكلة؟ يمكننا فرض عودة النتيجة المتوقعة من خلال غلاف مجهول (تعبير وظيفة التنفيذ الذاتي المجهول):
الدالة timemanage () {for (var i = 0 ؛ i <5 ؛ i ++) {(function (num) {setTimeOut (function () {console.log (num) ؛} ، 1000) ؛}) (i) ؛ }}أو إرجاع تعيين دالة مجهولة في وظيفة الإغلاق المجهول:
الدالة timemanage () {for (var i = 0 ؛ i <10 ؛ i ++) {setTimeOut ((function (e) {return function () {console.log (e) ؛}}) (i) ، 1000)}} // timmanager () ؛ الإخراج 1،2،3،4،5function createClosure () {var result = [] ؛ لـ (var i = 0 ؛ i <5 ؛ i ++) {result [i] = function (num) {return function () {console.log (num) ؛ } }(أنا)؛ } نتيجة الإرجاع ؛} // createClosure () [1] () الإخراج 1 ؛ CreateClosure () [2] () الإخراج 2سواء كانت غلاف مجهول أو وظائف مجهولة المصدر ، من حيث المبدأ ، حيث يتم تمرير الوظيفة بالقيمة ، سيتم نسخ قيمة المتغير الذي يتم نسخه إلى المعلمة الفعلية ، ويتم إنشاء وظيفة مجهولة داخل الوظيفة المجهولة لإرجاع NUM ، بحيث يكون لكل وظيفة نسخة من NUM ، والتي لن تؤثر على بعضها البعض.
هذا في الإغلاق
إيلاء اهتمام خاص عند استخدام هذا في عمليات الإغلاق ، لأن القليل من الإهمال قد يسبب مشاكل. عادة ما نفهم أن هذا الكائن ملزم بالوظيفة في وقت التشغيل. في الوظيفة العالمية ، هذا الكائن هو كائن نافذة. عندما يتم استدعاء الوظيفة كطريقة في الكائن ، فإن هذا يساوي هذا الكائن (يقوم Todo بعملية فرز حول هذا). نظرًا لأن نطاق الوظائف المجهولة هو عالمي ، فإن هذا الإغلاق يشير عادة إلى نافذة الكائن العالمي:
var scope = "global" ؛ var object = {scope: "local" ، getScope: function () {return function () {return this.scope ؛ }}}استدعاء Object.getScope () () إرجاع القيمة العالمية بدلاً من المحلية التي توقعناها. قلنا في وقت سابق أن الوظائف المجهولة الداخلية في الإغلاق ستحمل نطاق الوظيفة الخارجية ، فلماذا لا تحصل على هذه الوظيفة الخارجية؟ عندما يتم استدعاء كل وظيفة ، سيتم إنشاء هذه الوسيطات تلقائيًا. عند البحث عن وظائف داخلية مجهولة ، يبحثون عن المتغيرات التي نريدها في الكائن النشط. لذلك ، توقف عن البحث عن وظائف خارجية ، ولا يمكن أبدًا الوصول مباشرة إلى المتغيرات في الوظائف الخارجية. باختصار ، عندما تسمى وظيفة ما كوسيلة لكائن ما في إغلاق ، من المهم إيلاء اهتمام خاص أن هذا في الدالة المجهولة داخل الطريقة يشير إلى متغير عالمي.
لحسن الحظ ، يمكننا حل هذه المشكلة بكل بساطة ، ما عليك سوى تخزين هذا في نطاق الوظيفة الخارجية في متغير يمكن الوصول إليه عن طريق الإغلاق:
var scope = "global" ؛ var object = {scope: "local" ، getScope: function () {var that = this ؛ إرجاع وظيفة () {return that.scope ؛ }}} object.getscope () () () إرجاع القيمة المحلية.الذاكرة والأداء
نظرًا لأن الإغلاق يحتوي على مرجع سلسلة النطاق نفسه مثل سياق وقت تشغيل الوظيفة ، سيكون له تأثير سلبي معين. عندما يتم تدمير الكائن النشط وسياق وقت التشغيل في الوظيفة ، لا يمكن تدمير الكائن النشط لأنه لا يزال هناك إشارة إلى الكائن النشط ، مما يعني أن الإغلاق يشغل مساحة ذاكرة أكثر من الوظائف العادية ، وقد يتسبب أيضًا في تسرب الذاكرة في متصفح IE ، على النحو التالي:
دالة bindEvent () {var target = document.getElementById ("elem") ؛ target.onclick = function () {console.log (target.name) ؛ }}في المثال أعلاه ، تقوم الدالة المجهولة بإنشاء مرجع إلى هدف الكائن الخارجي. طالما وجود الوظيفة المجهولة ، لن تختفي المرجع ، ولن يتم تدمير الكائن المستهدف للوظيفة الخارجية ، مما يخلق مرجعًا دائريًا. يتمثل الحل في تقليل الإشارات الدائرية إلى المتغيرات الخارجية عن طريق إنشاء نسخة من Target.Name وإعادة ضبط الكائن يدويًا:
دالة bindEvent () {var target = document.getElementById ("elem") ؛ var name = target.name ؛ target.onclick = function () {console.log (name) ؛ } Target = null ؛ }إذا كان هناك إمكانية الوصول إلى المتغيرات الخارجية في الإغلاق ، فسيتم إضافة مسار البحث عن المعرفات بلا شك ، وفي ظل ظروف معينة ، سيؤدي ذلك أيضًا إلى خسائر في الأداء. لقد ذكرنا سابقًا: حاول تخزين المتغيرات الخارجية في المتغيرات المحلية لتقليل طول بحث سلاسل النطاق.
ملخص: عمليات الإغلاق ليست فريدة من نوعها لجافا سكريبت ، لكن لديها مظاهرها الفريدة في JavaScript. باستخدام عمليات الإغلاق ، يمكننا تحديد بعض المتغيرات الخاصة في JavaScript ، وحتى تقليد النطاقات على مستوى الكتلة. ومع ذلك ، أثناء استخدام الإغلاق ، نحتاج أيضًا إلى فهم المشكلات الحالية من أجل تجنب المشكلات غير الضرورية.