أنا أشاهد ممارسة ES2015 مؤخرًا ، وهناك قوله يقول
لا يوجد نطاق على مستوى الكتلة في JavaScript
قد لا تفهم هذه المشكلة ، دعنا نلقي نظرة على مثال أولاً
var a = [] لـ (var i = 0 ؛ i <10 ؛ i ++) {a [i] = function () {console.log (i) ؛ }} a [6] () ؛أعتقد أن الكثير من الناس يعتقدون أن نتيجة هذا السؤال هي 6 ، لكن للأسف ، الإجابة هي 10. جرب شيئًا آخر. A [7] () ، A [8] () ، و A [8] () كلها 10 !!
نظرًا لأن JS غالبًا ما يلف المتغيرات البدائية في كائنات مقابلة عند المعالجة ، على سبيل المثال ، لـ var str = "Hello World" ؛ str.slice (1).
ربما تكون العملية الحقيقية لـ JS هي var str = "Hello World" ؛ سلسلة جديدة (STR) .Slice (1). مثل هذه العملية قد تتسبب في مشكلة بالنسبة لنا لفهم المشكلة.
من أجل شرح هذه المشكلة هنا ، أنا أنتمي إلى نوع الرقم في النوع البدائي ، وأنا أعلن صراحة كنوع الرقم. نظرًا لأن عملية المهمة الخاصة بالنوع الأساسي هي إعادة تطبيق الذاكرة وتعديل اتجاه المتغير ، فإننا نستخدم أيضًا عملية إعادة الكائن الجديد لمحاكاة هذه العملية. الرمز المعدل كما يلي:
var a = [] var i = new number (0) ؛ for (؛ i <10 ؛ i = number number (i+1)) {a [i] = function () {console.log (i.toString ()) ؛ }} a [6] () ؛ // 10A [7] () ؛ // 10A [8] () ؛ // 10A [9] () ؛ // 10A [9] () ؛ // 10دعونا نلقي نظرة على عناوين الذاكرة النسبية لهذه المتغيرات بالاشتراك مع البرنامج.
(function () {var id = 0 ؛ function generateId () {return id ++ ؛} ؛ object.prototype.id = function () {var newId = generateId () ؛ this.id = function () {return newId ؛} ؛ رقم جديد (i+1) ، i.id ()) {a [i] = function () {console.log (i.id ()) ؛ console.log (i.toString ()) ؛ }} a [6] () ؛ // 10 10a [7] () ؛ // 10 10a [8] () ؛ // 10 10a [9] () ؛ // 10 10console.log (i.id ()) // 10لقد قمنا هنا بالفعل بمحاكاة تأثير "المهمة" بأكمله لـ I ، والعنوان النسبي لـ I قد تغير من 0 إلى 10 (في النهاية ، يجب إضافته مرة واحدة قبل أن يتمكن من القفز من الحلقة).
أثناء النظر إلى العنوان النسبي لـ I ، وجدنا مشكلة: عندما يتم تنفيذ الوظيفة المقابلة لـ [x] (x: 0 ~ 9) ، العنوان النسبي لـ I Compreed هو 10. لماذا؟
سنشمل هنا قضية نطاق مستوى الكتلة. هنا نقتبس من ممر من روان ييفنغ في مقدمة ES6:
ES5 لديها فقط نطاق عالمي ونطاق وظائف ، ولكن لا يوجد نطاق على مستوى الكتلة.
ES5 هي النسخة الأكثر استخدامًا من JS. هذه الجملة تقول أنه في جافا سكريبت ، لا يوجد نطاق كتلة. لا يوجد سوى نطاق عالمي ونطاق على مستوى الكتلة.
كيف تفهم؟ على سبيل المثال
لـ (var i = 0 ؛ i <10 ؛ i ++) {console.log (i) ؛} console.log (i) ؛ // 10console.log (window.i) ؛ // 10بشكل حدسي ، نعتقد أن حلقة for هي كتلة رمز ويجب أن تنتمي إلى نطاق مستوى الكتلة. ومع ذلك ، لا يمكن فقط الإخراج من 0 إلى 9 بشكل طبيعي ، ولكن يمكن أيضًا إخراج 10 خارجيًا في الحلقة. في الوقت نفسه ، وجدنا أنه على الرغم من أننا حددنا على الحلقة ، يبدو أنني معلق على كائن النافذة العالمي (إذا كانت بيئة تنفيذ NodeJs ، فسيتم معلقها على الكائن العالمي)
لذلك ، لن يكون للكتل مثل الحلقات في JavaScript تأثير نطاق مستوى الكتلة. لا يختلف تحديد المتغيرات في كتل التعليمات البرمجية مثل الحلقات عن المتغيرات التي تحدد مباشرة في النطاق الحالي.
لكن يمكننا عزل النطاق من خلال الوظائف:
(function () {for (var i = 0 ؛ i <10 ؛ i ++) {console.log (i) ؛} console.log (i) ؛}) () console.log (i) ؛ /// i غير محددةفي نفس الوقت ، إذا console.log (window.i) ؛ يتم تنفيذها ، وسيتم الحصول على النتيجة غير المحددة. هنا نستخدم وظيفة التنفيذ الفورية لتشكيل نطاق. يلعب دورًا مشابهًا لكتلة الكود. بعد نطاق الوظيفة هذا ، لم يعد بإمكاني الوصول إلى المتغير. ومع ذلك ، يمكن الوصول إلي في أي وقت ضمن نطاق الوظيفة.
ارجع إلى السؤال السابق ، وبعد ذلك سنفهمه بالاشتراك مع النطاق العالمي ونطاق مستوى الكتلة فقط في JavaScript. في الحلقة ، يجب تعريف IDEDED في النطاق الحالي ، أي نطاق النافذة. في جسم الحلقة ، نقوم بتعيين وظيفة لـ [i]. عندما ننفذ هذه الوظيفة ، يكون الموقف كما يلي:
أنا غير موجود في الوظيفة ، لذلك نتبع سلسلة النطاق للعثور على أنا. أنا نخرج في هذا الوقت هو هذا أنا. نظرًا لأنني أخرج من آخر +1 من الحلقة ، أصبحت 10 ، وبالتالي فإن نتيجة الإخراج هي دائمًا 10. لكن ما نحتاجه حقًا ليس آخر أنا ، لكنني في العملية الوسيطة. إذا أردنا حل هذه المشكلة ، فنحن بحاجة إلى وضع المتغير I جانباً (لأن آخر ما أصبح حتماً 10). نحتاج إلى السماح للوظيفة المقابلة بـ [0] الرجوع إلى القيمة 0 ، ودع الوظيفة المقابلة لـ [1] تشير إلى القيمة 1. كما هو موضح في الشكل أدناه:
العودة إلى الكود السابق لدينا.
يوضح السهم في الشكل أنه يمكننا الوصول إلى I (0 ~ 9). هنا ، نظرًا لأن الحلقة لا تشكل نطاقًا على مستوى الكتلة في حد ذاته ، فإننا نصل إلى المحدد بواسطة الحلقة لـ for عندما نتبع سلسلة النطاق.
هنا نلتف رمزنا بوظيفة تنفيذ فورية ، والتي يمكن أن تشكل نطاقًا ، ونمرر القيمة الأولى لذلك. ما يلي:
var a = [] var i = new number (0) ؛ console.log (i.id ()) ؛ // 0for (؛ i <10 ؛ i = new number (i+1) ، i.id ()) {(function (i) {a [i] = function () {console.log (i.id ()) ؛ console.log ( // 6 6a [7] () ؛ // 7 7a [8] () ؛ // 8 8a [9] () ؛ // 9 9console.log (i.id ()) ؛ // 10}نظرًا لأن وظيفة التنفيذ الفورية هذه تشير إلى القيمة العددية 0 ~ 9 ، عندما نقوم بتنفيذ الوظيفة A [i] ، سنجد أولاً نطاق وظيفة التنفيذ الفوري على طول سلسلة النطاق. تحافظ وظيفة التنفيذ الفوري على المرجع العددي من 0 ~ 9 ، ويمكننا إخراج قيمة I في الوظيفة A [i] بشكل صحيح. من خلال نتيجة التنفيذ ، يمكننا أن نرى أنه ليس فقط نتيجة التنفيذ صحيحة ، ولكن عنوان الذاكرة النسبية للقيمة التي نشير إليها صحيحة أيضًا. ثم نغير كائن الأرقام الذي تم الإعلان عنه صراحةً للاختبار. على النحو التالي:
var a = [] ؛ for (var i = 0 ؛ i <10 ؛ i ++) {(function (i) {a [i] = function () {console.log (i) ؛}}) (i) ؛}أخيرًا ، دعونا نلقي نظرة على بناء جملة ES6 الموصى به باستخدام Let بدلاً من VAR وتجميع وإنشاء رمز ES5 من خلال Baable:
// es6 code var a = [] for (let i = 0 ؛ i <10 ؛ i ++) {a [i] = function () {console.log (i) ؛ }} a [6] () ؛ // babel codized and endered es5 code "استخدم صارم" ؛ var a = [] } ؛} ؛ for (var i = 0 ؛ i <10 ؛ i ++) {_loop (i) ؛} a [6] () ؛دعونا نرى ما إذا كان حلنا مشابهًا جدًا لحل ES6. هنا وظيفة التنفيذ الفورية لدينا تعادل تنفيذ وظيفة _loop و _loop (i) في رمز ES5 الذي تم إنشاؤه.