javascript - مميزات - إغلاق جافا سكريبت داخل حلقات-مثال عملي بسيط




مميزات الجافا سكريبت (20)

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = function() {          // and store them in funcs
    console.log("My value: " + i); // each should log its value.
  };
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

يخرج هذا:

قيمي: 3
قيمي: 3
قيمي: 3

في حين أنني أرغب في الإخراج:

قيمي: 0
قيمي: 1
قيمي: 2

تحدث نفس المشكلة عند حدوث تأخير في تشغيل الدالة باستخدام مستمعي الأحداث:

var buttons = document.getElementsByTagName("button");
for (var i = 0; i < buttons.length; i++) {          // let's create 3 functions
  buttons[i].addEventListener("click", function() { // as event listeners
    console.log("My value: " + i);                  // each should log its value.
  });
}
<button>0</button><br>
<button>1</button><br>
<button>2</button>

... أو رمز غير متزامن ، على سبيل المثال باستخدام Promises:

// Some async wait function
const wait = (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms));

for(var i = 0; i < 3; i++){
  wait(i * 100).then(() => console.log(i)); // Log `i` as soon as each promise resolves.
}

ما هو الحل لهذه المشكلة الأساسية؟


ونحن سوف تحقق، ما يحدث فعلا عندما تقوم بتعريف varو letاحدا تلو الآخر.

الحالة 1 : استخدامvar

<script>
   var funcs = [];
   for (var i = 0; i < 3; i++) {
     funcs[i] = function () {
        debugger;
        console.log("My value: " + i);
     };
   }
   console.log(funcs);
</script>

الآن افتح نافذة وحدة التحكم في Chrome بالضغط على F12 وتحديث الصفحة. قم بإنفاق كل 3 وظائف داخل الصفيف. سترى خاصية تسمى [[Scopes]].Expand ذلك. سترى كائن صفيف واحد يسمى "Global"، وتوسيع هذا واحد. ستجد خاصية 'i'معلنة في الكائن ذي القيمة 3.

استنتاج:

  1. عندما تقوم بتعريف متغير باستخدام 'var'خارج وظيفة ، يصبح المتغير الشامل (يمكنك التحقق من خلال الكتابة iأو window.iفي نافذة وحدة التحكم. وسوف يعود 3).
  2. ﻟﻦ ﺗﺘﺼﻞ اﻟﻮﻇﻴﻔﺔ اﻟﻤﺬآﻮرة اﻟﺘﻲ ﻗﻤﺖ ﺑﺈﻋﻼﻧﻬﺎ ﺑﺎﻟﺘﺤﻘﻖ ﻣﻦ اﻟﻘﻴﻤﺔ داﺧﻞ اﻟﻮﻇﻴﻔﺔ واﻟﺘﺤﻘﻖ ﻣﻨﻬﺎ إﻻ إذا اﺳﺘﺪﻋﺖ اﻟﻮﻇﺎﺋﻒ
  3. عند استدعاء الدالة ، console.log("My value: " + i)تأخذ القيمة من Globalالكائن الخاص بها وتعرض النتيجة.

CASE2: استخدام السماح

الآن استبدال 'var'مع'let'

<script>
    var funcs = [];
    for (let i = 0; i < 3; i++) {
        funcs[i] = function () {
           debugger;
           console.log("My value: " + i);
        };
    }
    console.log(funcs);
</script>

افعل نفس الشيء ، اذهب إلى النطاقات. الآن سترى كائنين "Block"و "Global". الآن قم بتوسيع Blockالكائن ، سترى 'i' معرّفة هناك ، والشيء الغريب هو أنه ، لكل وظيفة ، القيمة إذا كانت iمختلفة (0 ، 1 ، 2).

استنتاج:

عندما تقوم بتعريف متغير باستخدام 'let'حتى خارج الوظيفة ولكن داخل الحلقة ، لن يكون هذا المتغير متغيرًا عالميًا ، سيصبح Blockمتغير مستوى متاح فقط لنفس الوظيفة فقط. هذا هو السبب ، نحن نحصل على قيمة iمختلفة لكل وظيفة عندما نستدعي الوظائف.

لمزيد من التفاصيل حول كيفية عمل أقرب ، يرجى الدخول من خلال البرنامج التعليمي الفيديو الرائع https://youtu.be/71AtaJpJHw0


حاول هذا أقصر

  • لا صفيف

  • لا إضافية للحلقة


for (var i = 0; i < 3; i++) {
    createfunc(i)();
}

function createfunc(i) {
    return function(){console.log("My value: " + i);};
}

http://jsfiddle.net/7P6EN/


إليك حل بسيط يستخدم forEach (يعمل مرة أخرى على IE9):

var funcs = [];
[0,1,2].forEach(function(i) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
})
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

مطبوعات:

My value: 0
My value: 1
My value: 2

استخدام تعبير دالة مستدعاة فوراً ، الطريقة الأبسط والأكثر قابلية لإدراج متغير فهرس:

for (var i = 0; i < 3; i++) {

    (function(index) {
        console.log('iterator: ' + index);
        //now you can also loop an ajax call here 
        //without losing track of the iterator value: $.ajax({});
    })(i);

}

هذا يرسل المكرر في الدالة المجهولة التي نحددها index . يؤدي هذا إلى إغلاق ، حيث يتم حفظ المتغير ليتم استخدامه لاحقًا في أي وظيفة غير متزامنة داخل IIFE.


المشكلة الرئيسية في الشفرة التي يعرضها البروتوكول الاختياري هي i لا أقرأ أبدا حتى الحلقة الثانية. للتدليل ، تخيل رؤية خطأ داخل الشفرة

funcs[i] = function() {            // and store them in funcs
    throw new Error("test");
    console.log("My value: " + i); // each should log its value.
};

الخطأ لا يحدث في الواقع حتى يتم تنفيذ funcs[someIndex] () . باستخدام هذا المنطق نفسه ، يجب أن يكون واضحًا أن قيمة i لا يتم جمعها حتى هذه النقطة أيضًا. بمجرد انتهاء الحلقة الأصلية ، فإن i++ يجلب i إلى قيمة 3 التي ينتج عنها الشرط i < 3 الفشل ونهاية الحلقة. عند هذه النقطة ، i هو 3 وهكذا عندما يتم استخدام funcs[someIndex]() ويتم funcs[someIndex]() ، فإنه 3 - في كل مرة.

لتجاوز هذا ، يجب عليك تقييم i كما هو مصادفة. لاحظ أن هذا قد حدث بالفعل في شكل funcs[i] (حيث يوجد 3 فهارس فريدة). هناك عدة طرق لالتقاط هذه القيمة. واحد هو تمريرها كمعلمة لدالة تظهر في عدة طرق هنا بالفعل.

خيار آخر هو بناء كائن وظيفة والتي سوف تكون قادرة على إغلاق أكثر من المتغير. يمكن أن يتحقق ذلك على هذا النحو

jsFiddle Demo

funcs[i] = new function() {   
    var closedVariable = i;
    return function(){
        console.log("My value: " + closedVariable); 
    };
};

تأخرت قليلاً في الحفلة ، لكنني كنت أستكشف هذه المسألة اليوم ولاحظت أن العديد من الإجابات لا تتناول بالكامل كيف تعالج جافا سكريبت النطاقات ، وهو ما يتلخص في جوهره.

وكما ذكر العديد من الأشخاص الآخرين ، فإن المشكلة تكمن في أن الدالة الداخلية تشير إلى متغير i ذاته. فلماذا لا نقوم فقط بإنشاء متغير محلي جديد كل عملية تكرار ، ولدينا مرجع الدالة الداخلية بدلاً من ذلك؟

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    var ilocal = i; //create a new local variable
    funcs[i] = function() {
        console.log("My value: " + ilocal); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

تماما كما كان من قبل ، حيث خرجت كل وظيفة داخلية عن القيمة الأخيرة التي تم تعيينها إلى i ، فإن كل دالة داخلية تقوم الآن بإخراج القيمة الأخيرة المخصصة ل ilocal . ولكن لا ينبغي أن يكون كل التكرار ilocal الخاصة؟

اتضح ، هذه هي القضية. كل التكرار يشترك في نفس النطاق ، لذلك كل التكرار بعد الأول هو مجرد الكتابة ilocal . من MDN :

مهم: لا يحتوي جافا سكريبت على نطاق الحظر. يتم تحديد المتغيرات التي يتم إدخالها مع كتلة إلى وظيفة أو نص يحتوي على ، وتأثيرات وضع لها تستمر خارج الكتلة نفسها. بمعنى آخر ، لا تقدم عبارات الحظر نطاقًا. على الرغم من أن الكتل "المستقلة" هي بناء صحيح ، إلا أنك لا تريد استخدام كتل مستقلة في جافا سكريبت ، لأنها لا تفعل ما تعتقد أنه يفعل ، إذا كنت تعتقد أنها تفعل مثل هذه الكتل في C أو Java.

تمت إعادة التأكيد للتأكيد:

جافا سكريبت ليس لديها نطاق الكتلة. يتم تحديد المتغيرات المقدمة مع كتلة إلى الوظيفة المحتوية أو البرنامج النصي

يمكننا رؤية ذلك من خلال التحقق من ilocal قبل إعلانه في كل مرة:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
  console.log(ilocal);
  var ilocal = i;
}

هذا هو بالضبط السبب في أن هذا الخطأ صعب جدا. على الرغم من أنك تقوم بإعادة تحديد متغير ، لن تقوم Javascript بطرح خطأ ، ولن تقوم JSLint بطرح تحذير. هذا هو السبب أيضا في أن أفضل طريقة لحل هذا هو الاستفادة من الإغلاق ، والتي هي في الأساس فكرة أنه في جافا سكريبت ، وظائف داخلية الوصول إلى المتغيرات الخارجية لأن النطاقات الداخلية "أرفق" النطاقات الخارجية.

وهذا يعني أيضًا أن الدوال الداخلية "تمسك" بالمتغيرات الخارجية وتبقيها على قيد الحياة ، حتى إذا عادت الدالة الخارجية. للاستفادة من ذلك ، فإننا نقوم بإنشاء واستدعاء وظيفة مجمّع ilocal نطاق جديد تمامًا ، وإعلان ilocal في النطاق الجديد ، وإرجاع دالة داخلية تستخدم ilocal (مزيد من الشرح أدناه):

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = (function() { //create a new scope using a wrapper function
        var ilocal = i; //capture i into a local var
        return function() { //return the inner function
            console.log("My value: " + ilocal);
        };
    })(); //remember to run the wrapper function
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

إن إنشاء الوظيفة الداخلية داخل دالة المجمع يعطي الوظيفة الداخلية بيئة خاصة لا يمكنها الوصول إليها إلا "إغلاق". وهكذا ، في كل مرة نطلق عليها الدالة wrapper ، نقوم بإنشاء وظيفة داخلية جديدة مع بيئة منفصلة خاصة بها ، مما يضمن أن المتغيرات ilocal لا تصطدم ilocal فوق بعضها البعض. يعطي بعض التحسينات الطفيفة الإجابة النهائية التي قدمها العديد من مستخدمي SO الآخرين:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (var i = 0; i < 3; i++) {
    funcs[i] = wrapper(i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}
//creates a separate environment for the inner function
function wrapper(ilocal) {
    return function() { //return the inner function
        console.log("My value: " + ilocal);
    };
}

تحديث

مع تعميم ES6 الآن ، أصبح بإمكاننا الآن استخدام الكلمة الأساسية الجديدة let بإنشاء متغيرات النطاق المحظور:

//overwrite console.log() so you can see the console output
console.log = function(msg) {document.body.innerHTML += '<p>' + msg + '</p>';};

var funcs = {};
for (let i = 0; i < 3; i++) { // use "let" to declare "i"
    funcs[i] = function() {
        console.log("My value: " + i); //each should reference its own local variable
    };
}
for (var j = 0; j < 3; j++) { // we can use "var" here without issue
    funcs[j]();
}

انظروا كم هو سهل الآن! لمزيد من المعلومات ، راجع هذه الإجابة ، التي تستند معلوماتي إليها.


حسنًا ، المشكلة هي أن المتغير i ، ضمن كل من وظائفك المجهولة ، مرتبط بنفس المتغير خارج الوظيفة.

الحل الكلاسيكي: الإغلاق

ما تريد القيام به هو ربط المتغير داخل كل دالة إلى قيمة منفصلة غير متغيرة خارج الدالة:

var funcs = [];

function createfunc(i) {
    return function() { console.log("My value: " + i); };
}

for (var i = 0; i < 3; i++) {
    funcs[i] = createfunc(i);
}

for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

نظرًا لعدم وجود نطاق كتلة في JavaScript - نطاق الوظائف فقط - عن طريق التفاف إنشاء الدالة في وظيفة جديدة ، فإنك تتأكد من بقاء قيمة "i" كما تريد.

2015 الحل: forEach

من خلال توفر Array.prototype.forEach (عام 2015) على نطاق واسع نسبياً ، تجدر الإشارة إلى أنه في تلك الحالات التي تنطوي على التكرار بشكل أساسي على مجموعة من القيم ، يوفر .forEach() طريقة نظيفة وطبيعية للحصول على إغلاق مميز كل التكرار. وهذا يعني ، بافتراض أنك حصلت على نوع من الصفيف يحتوي على قيم (مراجع DOM ، كائنات ، أيا كان) ، والمشكلة تنشأ من إعداد معاودة الاتصال الخاصة بكل عنصر ، يمكنك القيام بذلك:

var someArray = [ /* whatever */ ];
// ...
someArray.forEach(function(arrayElement) {
  // ... code code code for this one element
  someAsynchronousFunction(arrayElement, function() {
    arrayElement.doSomething();
  });
});

الفكرة هي أن كل استدعاء لوظيفة رد الاتصال المستخدمة مع حلقة .forEach سيكون إغلاقها الخاص. المعلمة التي تم تمريرها إلى ذلك المعالج هي عنصر الصفيف الخاص بهذه الخطوة المحددة للتكرار. إذا تم استخدامه في رد اتصال غير متزامن ، فلن يتصادم مع أي من عمليات الاسترجاعات الأخرى المحددة في خطوات أخرى للتكرار.

إذا كنت تعمل في jQuery ، تعطيك وظيفة $.each() قدرة مماثلة.

حل ES6: let

تم الآن بدء تشغيل ECMAScript 6 (ES6) ، وهو أحدث إصدار من JavaScript ، في العديد من المتصفحات وأنظمة الواجهة الخلفية دائمة الخضرة. هناك أيضًا متسللون مثل Babel سيقومون بتحويل ES6 إلى ES5 للسماح باستخدام الميزات الجديدة على الأنظمة القديمة.

يقدم ES6 كلمات رئيسية جديدة للدعايات والثوابت التي يتم تحديد نطاقها بشكل مختلف عن المتغيرات المستندة إلى var . على سبيل المثال ، في حلقة مع فهرس يستند إلى let ، سيكون لكل تكرار من خلال الحلقة قيمة جديدة من i حيث يتم تحديد نطاق كل قيمة داخل الحلقة ، بحيث تعمل شفرتك كما تتوقع. هناك العديد من الموارد ، ولكني أوصيك بإمكانية تحديد نطاق 2ality كمصدر كبير للمعلومات.

for (let i = 0; i < 3; i++) {
    funcs[i] = function() {
        console.log("My value: " + i);
    };
}

على الرغم من ذلك ، كن حذرا ، فإن IE9-IE11 و Edge قبل دعم Edge 14 يسمحان لك بالحصول على الخطأ أعلاه (لا يقوما بإنشاء كلمة i جديدة في كل مرة ، لذا فإن كل الوظائف المذكورة أعلاه سوف تسجل 3 كما لو كانت تستخدم var ). أيدج 14 أخيرا يحصل على حق.


طريقة أخرى لم يتم ذكرها بعد هي استخدام Function.prototype.bind

var funcs = {};
for (var i = 0; i < 3; i++) {
  funcs[i] = function(x) {
    console.log('My value: ' + x);
  }.bind(this, i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();
}

تحديث

كما هو موضح بواسطةsquint وmekdev ، يمكنك الحصول على أداء أفضل عن طريق إنشاء الدالة خارج الحلقة أولاً ثم ربط النتائج داخل الحلقة.

function log(x) {
  console.log('My value: ' + x);
}

var funcs = [];

for (var i = 0; i < 3; i++) {
  funcs[i] = log.bind(this, i);
}

for (var j = 0; j < 3; j++) {
  funcs[j]();
}


محاولة:

var funcs = [];

for (var i = 0; i < 3; i++) {
    funcs[i] = (function(index) {
        return function() {
            console.log("My value: " + index);
        };
    }(i));
}
for (var j = 0; j < 3; j++) {
    funcs[j]();
}

تحرير (2014):

أنا شخصياً أعتقد أن الإجابة .bind على @ Aust حول استخدام .bind هي أفضل طريقة للقيام بهذا النوع من الأشياء الآن. هناك أيضا lo-dash / الشرطة السفلية ' _.partial عندما لا تحتاج أو تريد الفوضى مع bind thisArg .


مع دعم ES6 الآن على نطاق واسع ، تغيرت أفضل إجابة على هذا السؤال. يوفر ES6 الكلمات الأساسية let و const أجل هذا الظرف بالضبط. بدلاً من العبث بالإغلاق ، يمكننا فقط استخدام let لتعيين متغير نطاق loop مثل هذا:

var funcs = [];
for (let i = 0; i < 3; i++) {          
    funcs[i] = function() {            
      console.log("My value: " + i); 
    };
}

ثم يشير val إلى كائن خاص بهذا المنعطف الخاص للحلقة ، وسيقوم بإرجاع القيمة الصحيحة بدون تدوين الإغلاق الإضافي. هذا واضح بشكل واضح يبسط هذه المشكلة.

يشبه const مع التقييد الإضافي الذي لا يمكن استعادة اسم المتغير إلى مرجع جديد بعد التعيين الأولي.

أصبح دعم المتصفح الآن متاحًا لأولئك الذين يستهدفون أحدث إصدارات المتصفحات. يتم دعم const / let حاليًا في أحدث إصدارات Firefox و Safari و Edge و Chrome. كما أنه مدعوم في Node ، ويمكنك استخدامه في أي مكان من خلال الاستفادة من أدوات البناء مثل Babel. يمكنك الاطلاع على مثال عملي هنا: http://jsfiddle.net/ben336/rbU4t/2/

المستندات هنا:

على الرغم من ذلك ، كن حذرا ، فإن IE9-IE11 و Edge قبل دعم Edge 14 يسمحان لك بالحصول على الخطأ أعلاه (لا يقوما بإنشاء كلمة i جديدة في كل مرة ، لذا فإن كل الوظائف المذكورة أعلاه سوف تسجل 3 كما لو كانت تستخدم var ). أيدج 14 أخيرا يحصل على حق.


وهناك طريقة أخرى للقول بأن ذلك هو أن i في وظيفتك ملزم في وقت تنفيذ الوظيفة ، وليس وقت إنشاء الوظيفة.

عند إنشاء الإغلاق ، i هو مرجع للمتغير المحدد في النطاق الخارجي ، وليس نسخة منه كما كان عند إنشاء الإغلاق. سيتم تقييمه في وقت التنفيذ.

توفر معظم الإجابات الأخرى طرقًا للتغلب عليها من خلال إنشاء متغير آخر لا يغير القيمة بالنسبة لك.

ظننت أنني سأضيف تفسيرا للوضوح. للحصول على حل ، شخصيا ، سأذهب مع هارتو لأنها الطريقة الأكثر تميزا في شرح ذلك من الإجابات هنا. أي من التعليمات البرمجية المنشورة ستعمل ، لكنني سأختار مصنع إغلاق بسبب الحاجة إلى كتابة كومة من التعليقات لشرح سبب إعلان متغير جديد (Freddy و 1800) أو بناء الجملة الغريبة المضمنة (apphacker).


مكافحة كونها بريئة

دعونا تحديد وظائف رد الاتصال على النحو التالي:

// ****************************
// COUNTER BEING A PRIMITIVE
// ****************************
function test1() {
    for (var i=0; i<2; i++) {
        setTimeout(function() {
            console.log(i);
        });
    }
}
test1();
// 2
// 2

بعد انتهاء المهلة ، ستقوم بطباعة 2 لكليهما. ويرجع ذلك إلى أن وظيفة رد الاتصال تصل إلى القيمة استنادًا إلى النطاق المفاهيمي ، حيث تم تعريف الدالة.

لتمرير والاحتفاظ بالقيمة أثناء تعريف الاتصال ، يمكننا إنشاء closure ، للحفاظ على القيمة قبل استدعاء معاودة الاتصال. ويمكن القيام بذلك على النحو التالي:

function test2() {
    function sendRequest(i) {
        setTimeout(function() {
            console.log(i);
        });
    }

    for (var i = 0; i < 2; i++) {
        sendRequest(i);
    }
}
test2();
// 1
// 2

الآن ما هو خاص في هذا هو "يتم تمرير الأوليات من خلال القيمة ونسخها. وهكذا عندما يتم تعريف الإغلاق ، فإنها تحتفظ بالقيمة من الحلقة السابقة."

مكافحة كونه كائن

بما أن الإغلاق يتمتع بإمكانية الوصول إلى متغيرات الوظيفة الأم عن طريق المرجع ، فإن هذا الأسلوب يختلف عن ذلك بالنسبة للأوليات.

// ****************************
// COUNTER BEING AN OBJECT
// ****************************
function test3() {
    var index = { i: 0 };
    for (index.i=0; index.i<2; index.i++) {
        setTimeout(function() {
            console.log('test3: ' + index.i);
        });
    }
}
test3();
// 2
// 2

لذا ، حتى إذا تم إنشاء إغلاق للمتغير الذي يتم تمريره ككائن ، فلن يتم الاحتفاظ بقيمة فهرس الحلقة. هذا لإظهار أنه لا يتم نسخ قيم كائن بينما يتم الوصول إليها عبر المرجع.

function test4() {
    var index = { i: 0 };
    function sendRequest(index, i) {
        setTimeout(function() {
            console.log('index: ' + index);
            console.log('i: ' + i);
            console.log(index[i]);
        });
    }

    for (index.i=0; index.i<2; index.i++) {
        sendRequest(index, index.i);
    }
}
test4();
// index: { i: 2}
// 0
// undefined

// index: { i: 2}
// 1
// undefined

الرمز الخاص بك لا يعمل ، لأن ما يفعله هو:

Create variable `funcs` and assign it an empty array;  
Loop from 0 up until it is less than 3 and assign it to variable `i`;
    Push to variable `funcs` next function:  
        // Only push (save), but don't execute
        **Write to console current value of variable `i`;**

// First loop has ended, i = 3;

Loop from 0 up until it is less than 3 and assign it to variable `j`;
    Call `j`-th function from variable `funcs`:  
        **Write to console current value of variable `i`;**  
        // Ask yourself NOW! What is the value of i?

الآن السؤال هو ، ما هي قيمة المتغير iعندما يتم استدعاء الدالة؟ لأنه يتم إنشاء الحلقة الأولى مع شرط i < 3، فإنه يتوقف على الفور عندما يكون الشرط غير صحيح ، لذلك هو i = 3.

يجب أن تفهم أنه في الوقت الذي يتم فيه إنشاء وظائفك ، لا يتم تنفيذ أي من التعليمات البرمجية الخاصة بها ، يتم حفظها فقط في وقت لاحق. وعندما يتم استدعاؤهم في وقت لاحق ، يقوم المترجم بتنفيذها ويسأل: "ما هي القيمة الحالية i؟"

لذا ، فإن هدفك هو أولاً حفظ قيمة iالوظيفة وفقط بعد حفظ الوظيفة funcs. يمكن القيام بذلك على سبيل المثال بهذه الطريقة:

var funcs = [];
for (var i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function(x) {            // and store them in funcs
        console.log("My value: " + x); // each should log its value.
    }.bind(null, i);
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

بهذه الطريقة ، سيكون لكل وظيفة متغير خاص بها xونقوم بتعيين هذا xإلى قيمة iكل تكرار.

هذه ليست سوى واحدة من الطرق المتعددة لحل هذه المشكلة.


مع ميزات جديدة من نطاق ES6 يتم إدارة نطاق المستوى:

var funcs = [];
for (let i = 0; i < 3; i++) {          // let's create 3 functions
    funcs[i] = function() {            // and store them in funcs
        console.log("My value: " + i); // each should log its value.
    };
}
for (let j = 0; j < 3; j++) {
    funcs[j]();                        // and now let's run each one to see
}

يتم استبدال الرمز في سؤال OP letبدلا من var.


أنا مندهش لا أحد حتى الآن اقترح استخدام forEachالوظيفة لتجنب (إعادة) استخدام المتغيرات المحلية بشكل أفضل. في الحقيقة ، أنا لا أستعمل for(var i ...)على الإطلاق بعد الآن لهذا السبب.

[0,2,3].forEach(function(i){ console.log('My value:', i); });
// My value: 0
// My value: 2
// My value: 3

// تم تعديله لاستخدامه forEachبدلاً من الخريطة.


استخدام هيكل closure ، وهذا من شأنه أن يقلل من حلقة إضافية. يمكنك القيام بذلك في حلقة واحدة للحلقة:

var funcs = [];
for (var i = 0; i < 3; i++) {     
  (funcs[i] = function() {         
    console.log("My value: " + i); 
  })(i);
}

بعد قراءة الحلول المختلفة ، أود أن أضيف أن سبب نجاح هذه الحلول هو الاعتماد على مفهوم سلسلة النطاق . إنها الطريقة التي تحل بها جافا سكريبت متغيرًا أثناء التنفيذ.

  • يشكل تعريف كل دالة نطاقًا يتكون من جميع المتغيرات المحلية التي تم الإعلان عنها بواسطة varوarguments .
  • إذا كان لدينا وظيفة داخلية محددة داخل وظيفة أخرى (خارجية) ، فهذا يشكل سلسلة ، وسيتم استخدامها أثناء التنفيذ
  • عند تنفيذ وظيفة ما ، يقوم وقت التشغيل بتقييم المتغيرات من خلال البحث في سلسلة النطاق . إذا كان من الممكن العثور على متغير في نقطة معينة من السلسلة ، فسيتوقف عن البحث واستخدامه ، وإلا يستمر حتى يصل النطاق العالمي الذي ينتمي إليهwindow .

في الكود الأولي:

funcs = {};
for (var i = 0; i < 3; i++) {         
  funcs[i] = function inner() {        // function inner's scope contains nothing
    console.log("My value: " + i);    
  };
}
console.log(window.i)                  // test value 'i', print 3

عندما funcsيتم التنفيذ ، ستكون سلسلة النطاق function inner -> global. نظرًا لتعذر iالعثور على المتغير في function inner(لم يتم الإعلان عن استخدام varأو تمريره كوسيطة) ، فإنه يستمر في البحث ، حتى iيتم العثور على القيمة في النهاية في النطاق العالميwindow.i .

عن طريق لفها في وظيفة خارجية إما بوضوح تعريف وظيفة مساعد مثل share أو استخدام وظيفة مجهولة مثل share فعل:

funcs = {};
function outer(i) {              // function outer's scope contains 'i'
  return function inner() {      // function inner, closure created
   console.log("My value: " + i);
  };
}
for (var i = 0; i < 3; i++) {
  funcs[i] = outer(i);
}
console.log(window.i)          // print 3 still

عندما funcsيتم تنفيذها ، ستكون سلسلة النطاق الآن function inner -> function outer. iيمكن العثور على هذا الوقت في نطاق الدالة الخارجي الذي يتم تنفيذه 3 مرات في حلقة for ، وفي كل مرة يكون iالحد مرتبطًا بشكل صحيح. لن تستخدم قيمةwindow.i عندما يتم تنفيذ الداخلية.

يمكن العثور على مزيد من التفاصيل here
يتضمن الخطأ الشائع في إنشاء إغلاق في الحلقة كما هو موجود here ، وكذلك سبب حاجتنا إلى الإغلاق والأداء.


تبدو العديد من الحلول صحيحة ولكنها لا تذكر أنها تسمى Curryingنمط تصميم برمجة وظيفي لحالات مثل هنا. 3-10 مرات أسرع من الربط حسب المتصفح.

var funcs = [];
for (var i = 0; i < 3; i++) {      // let's create 3 functions
  funcs[i] = curryShowValue(i);
}
for (var j = 0; j < 3; j++) {
  funcs[j]();                      // and now let's run each one to see
}

function curryShowValue(i) {
  return function showValue() {
    console.log("My value: " + i);
  }
}

شاهد كسب الأداء في المتصفحات المختلفة .


والحل الآخر: بدلاً من إنشاء حلقة أخرى ، قم فقط بربط thisوظيفة الإرجاع.

var funcs = [];

function createFunc(i) {
  return function() {
    console.log('My value: ' + i); //log value of i.
  }.call(this);
}

for (var i = 1; i <= 5; i++) {  //5 functions
  funcs[i] = createFunc(i);     // call createFunc() i=5 times
}

عن طريق ملزمة هذا ، يحل المشكلة كذلك.


يمكنك استخدام وحدة تعريفية لقوائم البيانات مثل query-js (*). في هذه الحالات ، أجد شخصياً أسلوباً إعلانياً أقل غرابة

var funcs = Query.range(0,3).each(function(i){
     return  function() {
        console.log("My value: " + i);
    };
});

يمكنك بعد ذلك استخدام الحلقة الثانية والحصول على النتيجة المتوقعة أو يمكنك القيام بها

funcs.iterate(function(f){ f(); });

(*) أنا مؤلف استقصاء js و لذلك متحيز لاستخدامه ، لذلك لا تأخذ كلماتي كتوصية للمكتبة المذكورة فقط للنهج الإعلاني :)







closures