javascript - लूप के अंदर जावास्क्रिप्ट बंद-सरल व्यावहारिक उदाहरण




loops closures (20)

हम जांच करेंगे, वास्तव में क्या होता है जब आप घोषित करते हैं 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>

अब F12 दबाकर और क्रोम कंसोल विंडो खोलें और पेज को रीफ्रेश करें। सरणी के अंदर हर 3 कार्यों का विस्तार करें। आपको एक संपत्ति दिखाई देगी जिसे एक्सपेन्ड कहा जाता है । आप एक सरणी वस्तु को देखेंगे , जिसे विस्तारित करें। आपको उस वस्तु में घोषित संपत्ति मिल जाएगी जिसमें मूल्य 3 है।[[Scopes]]"Global"'i'

निष्कर्ष:

  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

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>

... या असीमित कोड, उदाहरण के लिए वादे का उपयोग:

// 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.
}

इस मूल समस्या का समाधान क्या है?


इस छोटे से प्रयास करें

  • कोई सरणी नहीं

  • लूप के लिए कोई अतिरिक्त नहीं है


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

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

http://jsfiddle.net/7P6EN/


आपको समझने की आवश्यकता है कि जावास्क्रिप्ट में चर का दायरा फ़ंक्शन पर आधारित है। सी # कहने से यह एक महत्वपूर्ण अंतर है जहां आपके पास ब्लॉक स्कोप है, और सिर्फ वैरिएबल को कॉपी करने के लिए एक के अंदर कॉपी करना होगा।

एपैकर के उत्तर जैसे फ़ंक्शन को वापस करने का मूल्यांकन करने वाले फ़ंक्शन में इसे लपेटने से चाल चलती है, क्योंकि चर के पास फ़ंक्शन स्कोप होता है।

Var के बजाय एक लेट कीवर्ड भी है, जो ब्लॉक स्कोप नियम का उपयोग करने की अनुमति देगा। उस स्थिति में एक चर को परिभाषित करने के लिए चाल चलती है। उस ने कहा, चलो कीवर्ड संगतता के कारण व्यावहारिक समाधान नहीं है।

var funcs = {};
for (var i = 0; i < 3; i++) {
    let index = i;          //add this
    funcs[i] = function() {            
        console.log("My value: " + index); //change to the copy
    };
}
for (var j = 0; j < 3; j++) {
    funcs[j]();                        
}

इंडेक्स वैरिएबल को संलग्न करने के लिए सबसे सरल और सबसे पठनीय तरीका, तत्काल-आमंत्रित फ़ंक्शन अभिव्यक्ति का उपयोग करना:

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 रूप में परिभाषित करते 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++ को 3 के मान में लाता i जिसके परिणामस्वरूप i < 3 विफल रहता i < 3 और लूप समाप्त होता है। इस बिंदु पर, i 3 और इसलिए जब funcs[someIndex]() का उपयोग किया जाता है, और i मूल्यांकन किया जाता है, यह 3 - हर बार होता है।

इसे पाने के लिए, आपको इसका मूल्यांकन करना चाहिए जैसा कि इसका सामना करना पड़ रहा है। ध्यान दें कि यह पहले से ही funcs[i] के रूप में हुआ है funcs[i] (जहां 3 अद्वितीय अनुक्रमणिका हैं)। इस मूल्य को पकड़ने के कई तरीके हैं। एक इसे एक फ़ंक्शन के पैरामीटर के रूप में पास करना है जो पहले से ही कई तरीकों से दिखाया गया है।

एक अन्य विकल्प एक फ़ंक्शन ऑब्जेक्ट बनाना है जो चर को बंद करने में सक्षम होगा। इसे इस प्रकार पूरा किया जा सकता है

jsFiddle Demo

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

खैर, समस्या यह है कि परिवर्तनीय 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
}

चूंकि जावास्क्रिप्ट में कोई ब्लॉक स्कोप नहीं है - केवल फ़ंक्शन स्कोप - फ़ंक्शन सृजन को एक नए फ़ंक्शन में लपेटकर, आप सुनिश्चित करते हैं कि "i" का मान आपके इरादे से बना रहता है।

2015 समाधान: प्रत्येक के लिए

Array.prototype.forEach फ़ंक्शन (2015 में) की अपेक्षाकृत व्यापक उपलब्धता के साथ, यह ध्यान देने योग्य है कि उन परिस्थितियों में मुख्य रूप से मूल्यों की एक सरणी पर पुनरावृत्ति शामिल है .forEach() एक अलग, प्राकृतिक तरीका प्रदान करने के लिए एक स्वच्छ, प्राकृतिक तरीका प्रदान करता है हर पुनरावृत्ति। यही है, मान लीजिए कि आपके पास मूल्यों (डीओएम संदर्भ, वस्तुएं, जो कुछ भी) शामिल हैं, और प्रत्येक तत्व के लिए विशिष्ट कॉलबैक सेट करने की समस्या उत्पन्न होती है, तो आप यह कर सकते हैं:

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

विचार यह है कि कॉलबैक फ़ंक्शन का प्रत्येक आमंत्रण .forEach लूप के साथ उपयोग किया जाता है, यह स्वयं का बंद हो जाएगा। उस हैंडलर में पारित पैरामीटर पुनरावृत्ति के उस विशेष चरण के लिए विशिष्ट सरणी तत्व है। यदि इसका उपयोग एसिंक्रोनस कॉलबैक में किया जाता है, तो यह पुनरावृत्ति के अन्य चरणों में स्थापित किसी अन्य कॉलबैक से टकरा नहीं जाएगा।

यदि आप jQuery में काम करना चाहते हैं, तो $.each() फ़ंक्शन आपको एक समान क्षमता देता है।

ईएस 6 समाधान: let

ईसीएमएस्क्रिप्ट 6 (ईएस 6), जावास्क्रिप्ट का नवीनतम संस्करण, अब कई सदाबहार ब्राउज़र और बैकएंड सिस्टम में लागू किया जा रहा है। बैबेल जैसे ट्रांसपेलर भी हैं जो पुराने सिस्टम पर नई सुविधाओं के उपयोग की अनुमति देने के लिए ES6 से ES5 में परिवर्तित हो जाएंगे।

ES6 नए let और const कीवर्ड पेश करता है जो var -based चर से भिन्न रूप से स्कॉप्ड होते हैं। उदाहरण के लिए, let आधारित इंडेक्स के साथ एक लूप में, लूप के माध्यम से प्रत्येक पुनरावृत्ति के पास मेरा एक नया मान होगा जहां प्रत्येक मान लूप के अंदर स्कॉप्ड किया जाता है, इसलिए आपका कोड काम करेगा जैसा आप उम्मीद करते हैं। कई संसाधन हैं, लेकिन मैं सूचना के एक महान स्रोत के रूप में 2ality की ब्लॉक-स्कोपिंग पोस्ट की अनुशंसा करता हूं

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

सावधान रहें, हालांकि, एज 14 समर्थन से पहले IE9-IE11 और एज, ऊपर दिए गए गलत हैं (वे हर बार एक नया नहीं बनाते हैं, इसलिए ऊपर दिए गए सभी कार्य 3 लॉग होंगे जैसे कि अगर वे var उपयोग करते हैं)। एज 14 आखिरकार यह सही हो जाता है।


तकनीक पर एक और भिन्नता है, जो Bjorn's (apphacker) के समान है, जो आपको पैरामीटर के रूप में इसे पास करने के बजाय फ़ंक्शन के अंदर चर मान को असाइन करने देता है, जो कभी-कभी स्पष्ट हो सकता है:

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

ध्यान दें कि जो भी तकनीक आप उपयोग करते हैं, index वैरिएबल एक प्रकार का स्थिर चर बन जाता है, जो आंतरिक फ़ंक्शन की लौटाई गई प्रतिलिपि से बंधे होते हैं। हां, इसके मूल्य में परिवर्तन कॉल के बीच संरक्षित हैं। यह बहुत आसान हो सकता है।


पार्टी के लिए देर हो चुकी है, लेकिन मैं आज इस मुद्दे की खोज कर रहा था और देखा कि कई जवाब पूरी तरह से संबोधित नहीं करते हैं कि कैसे जावास्क्रिप्ट स्कॉप्स का व्यवहार करता है, जो अनिवार्य रूप से यह उबलता है।

तो जैसा कि कई अन्य लोगों ने उल्लेख किया है, समस्या यह है कि आंतरिक कार्य समान वैरिएबल का संदर्भ दे रहा है। तो हम प्रत्येक पुनरावृत्ति को एक नया स्थानीय चर क्यों नहीं बनाते हैं, और इसके बजाय आंतरिक फ़ंक्शन संदर्भ है?

//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 ओवरराइट कर ilocalMDN :

महत्वपूर्ण: जावास्क्रिप्ट में ब्लॉक स्कोप नहीं है। ब्लॉक के साथ पेश किए गए वेरिएबल्स को फ़ंक्शन या स्क्रिप्ट में स्कॉप्ड किया जाता है, और उन्हें सेट करने के प्रभाव ब्लॉक से परे बने रहते हैं। दूसरे शब्दों में, ब्लॉक स्टेटमेंट्स एक दायरा पेश नहीं करते हैं। यद्यपि "स्टैंडअलोन" ब्लॉक वैध वाक्यविन्यास हैं, लेकिन आप जावास्क्रिप्ट में स्टैंडअलोन ब्लॉक का उपयोग नहीं करना चाहते हैं, क्योंकि वे ऐसा नहीं करते हैं जो आप सोचते हैं कि वे करते हैं, अगर आपको लगता है कि वे सी या जावा में ऐसे ब्लॉक की तरह कुछ करते हैं।

जोर के लिए दोहराया गया:

जावास्क्रिप्ट में ब्लॉक स्कोप नहीं है। ब्लॉक के साथ पेश किए गए वेरिएबल्स को फ़ंक्शन या स्क्रिप्ट में स्कॉप्ड किया जाता है

हम इसे प्रत्येक पुनरावृत्ति में घोषित करने से पहले 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;
}

यही कारण है कि यह बग इतना मुश्किल है। भले ही आप एक चर को फिर से चला रहे हों, जावास्क्रिप्ट एक त्रुटि नहीं फेंक देगा, और जेएसलिंट एक चेतावनी भी नहीं फेंक देगा। यही कारण है कि इसे हल करने का सबसे अच्छा तरीका बंद करने का लाभ उठाना है, जो अनिवार्य रूप से विचार है कि जावास्क्रिप्ट में, आंतरिक कार्यों में बाहरी चरों तक पहुंच होती है क्योंकि आंतरिक क्षेत्र बाहरी क्षेत्रों को "संलग्न" करते हैं।

इसका यह भी अर्थ है कि आंतरिक कार्य बाहरी चर को "पकड़ते हैं" और उन्हें जीवित रखते हैं, भले ही बाहरी कार्य वापस आ जाए। इसका उपयोग करने के लिए, हम एक नया स्कोप बनाने के लिए पूरी तरह से एक रैपर फ़ंक्शन बनाते हैं और कॉल करते हैं, नए दायरे में 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]();
}

एक रैपर फ़ंक्शन के अंदर आंतरिक फ़ंक्शन बनाना आंतरिक कार्य को एक निजी वातावरण प्रदान करता है जो केवल "बंद" हो सकता है। इस प्रकार, हर बार जब हम रैपर फ़ंक्शन को कॉल करते हैं, तो हम अपने स्वयं के अलग वातावरण के साथ एक नया आंतरिक कार्य बनाते हैं, यह सुनिश्चित करते हुए कि 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);
    };
}

अद्यतन करें

ईएस 6 अब मुख्यधारा के साथ, अब हम ब्लॉक-स्कोप्ड चर बनाने के लिए नए 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 आपके फ़ंक्शन में फ़ंक्शन निष्पादित करने के समय फ़ंक्शन निष्पादित करने के समय बाध्य i

जब आप बंद करते हैं, तो i बाहरी दायरे में परिभाषित चर के संदर्भ में i , इसकी एक प्रति नहीं, क्योंकि जब आप बंद कर देते थे। निष्पादन के समय इसका मूल्यांकन किया जाएगा।

अन्य अधिकांश उत्तर एक और चर बनाकर काम करने के तरीके प्रदान करते हैं जो आपके लिए मूल्य नहीं बदलेगा।

बस सोचा कि मैं स्पष्टता के लिए एक स्पष्टीकरण जोड़ूंगा। एक समाधान के लिए, व्यक्तिगत रूप से, मैं हार्टो के साथ जाऊंगा क्योंकि यह यहां जवाबों से इसे करने का सबसे आत्म-व्याख्यात्मक तरीका है। पोस्ट किए गए कोड में से कोई भी काम करेगा, लेकिन मैं एक नई चर (फ्रेडी और 1800 के दशक) की घोषणा क्यों कर रहा हूं या अजीब एम्बेडेड क्लोजर सिंटैक्स (एपहेकर) क्यों घोषित कर रहा हूं, यह बताने के लिए टिप्पणियों का एक ढेर लिखने पर एक बंद कारखाने का चयन करूंगा।


यह जावास्क्रिप्ट में बंद करने के साथ सामान्य गलती का वर्णन करता है।

एक समारोह एक नए पर्यावरण को परिभाषित करता है

विचार करें:

function makeCounter()
{
  var obj = {counter: 0};
  return {
    inc: function(){obj.counter ++;},
    get: function(){return obj.counter;}
  };
}

counter1 = makeCounter();
counter2 = makeCounter();

counter1.inc();

alert(counter1.get()); // returns 1
alert(counter2.get()); // returns 0

हर बार makeCounter {counter: 0} का आह्वान किया जाता है, {counter: 0} परिणामस्वरूप एक नई वस्तु makeCounter है। इसके अलावा, नई वस्तु का संदर्भ देने के लिए obj की एक नई प्रति भी बनाई गई है। इस प्रकार, counter1 और counter2 एक दूसरे से स्वतंत्र हैं।

लूप में बंद

लूप में बंद करने का उपयोग करना मुश्किल है।

विचार करें:

var counters = [];

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = {
      inc: function(){obj.counter++;},
      get: function(){return obj.counter;}
    }; 
  }
}

makeCounters(2);

counters[0].inc();

alert(counters[0].get()); // returns 1
alert(counters[1].get()); // returns 1

ध्यान दें कि counters[0] और counters[1] स्वतंत्र नहीं हैं। वास्तव में, वे एक ही obj पर काम करते हैं!

ऐसा इसलिए है क्योंकि लूप के सभी पुनरावृत्तियों में साझा obj की केवल एक प्रति है, शायद प्रदर्शन कारणों से। भले ही {counter: 0} प्रत्येक पुनरावृत्ति में एक नई वस्तु बनाता है, obj की एक ही प्रतिलिपि केवल नवीनतम वस्तु के संदर्भ के साथ अपडेट हो जाएगी।

समाधान एक अन्य सहायक समारोह का उपयोग करना है:

function makeHelper(obj)
{
  return {
    inc: function(){obj.counter++;},
    get: function(){return obj.counter;}
  }; 
}

function makeCounters(num)
{
  for (var i = 0; i < num; i++)
  {
    var obj = {counter: 0};
    counters[i] = makeHelper(obj);
  }
}

यह काम करता है क्योंकि फ़ंक्शन स्कोप में स्थानीय चर, साथ ही फ़ंक्शन तर्क चर, प्रविष्टि पर नई प्रतियां आवंटित की जाती हैं।

विस्तृत चर्चा के लिए, कृपया जावास्क्रिप्ट बंद करने के नुकसान और उपयोग देखें


सबसे सरल समाधान होगा,

के बजाय का उपयोग करने का:

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

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

जो 3 बार के लिए "2" अलर्ट करता है। ऐसा इसलिए है क्योंकि लूप के लिए अज्ञात फ़ंक्शंस बनाए गए हैं, उसी बंद होने पर, और उस बंद होने पर, i मान समान है। साझा बंद होने से रोकने के लिए इसका उपयोग करें:

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

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

इसके पीछे विचार, IIFE (तत्काल-आमंत्रित फ़ंक्शन अभिव्यक्ति) के साथ लूप के पूरे शरीर को encapsulating और पैरामीटर के रूप में new_i गुजर रहा है और इसे कैप्चर कर रहा है। चूंकि अज्ञात फ़ंक्शन तुरंत निष्पादित किया जाता है, इसलिए अनाम मान के अंदर परिभाषित प्रत्येक फ़ंक्शन के लिए i मान अलग होता है।

यह समाधान ऐसी किसी भी समस्या को फिट करने लगता है क्योंकि इस मुद्दे से पीड़ित मूल कोड में न्यूनतम परिवर्तन की आवश्यकता होगी। वास्तव में, यह डिजाइन द्वारा है, यह बिल्कुल कोई मुद्दा नहीं होना चाहिए!


काउंटर एक प्रामाणिक हो रहा है

आइए कॉलबैक फ़ंक्शन को निम्नानुसार परिभाषित करें:

// ****************************
// 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करते हैं।

यह इस समस्या को हल करने के कई तरीकों में से एक है।


सबसे पहले, समझें कि इस कोड के साथ क्या गलत है:

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
}

यहां जब funcs[]सरणी शुरू की जा रही है, iतो बढ़ी जा रही है, funcsसरणी शुरू की गई है और सरणी का आकार func3 हो जाता है, इसलिए i = 3,। अब जब funcs[j]()कहा जाता है, तो यह फिर से चर का उपयोग कर रहा है i, जिसे पहले ही 3 में बढ़ा दिया गया है।

अब इसे हल करने के लिए, हमारे पास कई विकल्प हैं। उनमें से दो नीचे हैं:

  1. हम प्रारंभ कर सकते हैं iके साथ letया एक नया चर इनिशिएलाइज़ indexसाथ letऔर यह के बराबर किया जाता i। तो जब कॉल किया जा रहा है, indexतो इसका उपयोग किया जाएगा और इसके दायरे को प्रारंभ करने के बाद खत्म हो जाएगा। और कॉल करने के लिए, indexफिर से शुरू किया जाएगा:

    var funcs = [];
    for (var i = 0; i < 3; i++) {          
        let index = i;
        funcs[i] = function() {            
            console.log("My value: " + index); 
        };
    }
    for (var j = 0; j < 3; j++) {
        funcs[j]();                        
    }
    
  2. अन्य विकल्प tempFuncवास्तविक कार्य को लौटने वाला एक परिचय पेश किया जा सकता है :

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

आप query-js (*) जैसे डेटा की सूचियों के लिए एक घोषणात्मक मॉड्यूल का उपयोग कर सकते हैं । इन स्थितियों में मुझे व्यक्तिगत रूप से एक घोषणात्मक दृष्टिकोण कम आश्चर्यजनक लगता है

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

फिर आप अपने दूसरे पाश का उपयोग कर सकते हैं और अपेक्षित परिणाम प्राप्त कर सकते हैं या आप कर सकते हैं

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

(*) मैं क्वेरी-जेएस के लेखक हूं और इसका उपयोग करने के पक्ष में पक्षपातपूर्ण हूं, इसलिए मेरे शब्दों को केवल घोषणात्मक दृष्टिकोण के लिए कहा लाइब्रेरी के लिए सिफारिश के रूप में न लें :)


आपका मूल उदाहरण काम नहीं करने का कारण यह है कि आपके द्वारा लूप में बनाए गए सभी बंद एक ही फ्रेम का संदर्भ देते हैं। असल में, केवल एक ही iचर के साथ एक ऑब्जेक्ट पर 3 विधियां हैं । वे सभी एक ही मूल्य मुद्रित।


कई समाधान सही प्रतीत होते हैं लेकिन वे इसका उल्लेख नहीं करते हैं, 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);
  }
}

विभिन्न ब्राउज़रों में प्रदर्शन लाभ देखें ।


मुझे आश्चर्य है कि किसी ने अभी भी 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मानचित्र के बजाय उपयोग करने के लिए संपादित ।


यह सवाल वास्तव में जावास्क्रिप्ट का इतिहास दिखाता है! अब हम ऑब्जेक्ट विधियों का उपयोग करके डॉम नोड्स से सीधे तीर फ़ंक्शंस के साथ ब्लॉक स्कोपिंग से बच सकते हैं और सीधे लूप को संभाल सकते हैं।

const funcs = [1, 2, 3].map(i => () => console.log(i));
funcs.map(fn => fn())

const buttons = document.getElementsByTagName("button");
Object
  .keys(buttons)
  .map(i => buttons[i].addEventListener('click', () => console.log(i)));
<button>0</button><br>
<button>1</button><br>
<button>2</button>


विभिन्न समाधानों के माध्यम से पढ़ने के बाद, मैं यह जोड़ना चाहता हूं कि उन समाधानों का कारण स्कोप श्रृंखला की अवधारणा पर भरोसा करना है । निष्पादन के दौरान जावास्क्रिप्ट एक चर को हल करने का तरीका है।

  • प्रत्येक फ़ंक्शन परिभाषा एक दायरे बनाती है जिसमें सभी स्थानीय चर शामिल हैं 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 बार निष्पादित किया जाता है, प्रत्येक बार मूल्य iसही ढंग से बाध्य होता है। यह window.iआंतरिक निष्पादित होने के मूल्य का उपयोग नहीं करेगा ।

यहां अधिक जानकारी मिल सकती here
इसमें लूप में बंद होने में सामान्य गलती शामिल है जो हमारे पास है, साथ ही हमें बंद होने और प्रदर्शन पर विचार करने की आवश्यकता क्यों है।





closures