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


2816

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

यह इसका आउटपुट देता है:

मेरा मूल्य: 3
मेरा मूल्य: 3
मेरा मूल्य: 3

जबकि मैं इसे आउटपुट के लिए पसंद करूंगा:

मेरा मूल्य: 0
मेरा मूल्य: 1
मेरा मूल्य: 2


एक ही समस्या तब होती है जब फ़ंक्शन श्रोताओं का उपयोग करके फ़ंक्शन को चलाने में देरी होती है:

var buttons = document.getElementsByTagName("button");
// let's create 3 functions
for (var i = 0; i < buttons.length; i++) {
  // as event listeners
  buttons[i].addEventListener("click", function() {
    // each should log its value.
    console.log("My value: " + i);
  });
}
<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++) {
  // Log `i` as soon as each promise resolves.
  wait(i * 100).then(() => console.log(i));
}

संपादित करें: यह for inऔर for ofलूप में भी स्पष्ट है :

const arr = [1,2,3];
const fns = [];

for(i in arr){
  fns.push(() => console.log(`index: ${i}`));
}

for(v of arr){
  fns.push(() => console.log(`value: ${v}`));
}

for(f of fns){
  f();
}

इस बुनियादी समस्या का हल क्या है?


55
funcsयदि आप संख्यात्मक सूचकांकों का उपयोग कर रहे हैं, तो आप निश्चित रूप से एक सरणी नहीं बनना चाहते हैं? बिलकुल चौकन्ना।
डैनमैन

23
यह वास्तव में भ्रामक समस्या है। यह लेख मुझे इसे समझने में मदद करता है । शायद यह दूसरों की मदद भी करे।
user3199690

4
एक और सरल और स्पष्ट समाधान: 1) नेस्टेड फ़ंक्शंस के पास "ऊपर" के दायरे तक पहुंच है ; 2) एक क्लोजर सॉल्यूशन ... "एक क्लोजर एक फंक्शन होता है जिसमें पैरेंट फंक्शन बंद होने के बाद भी पैरेंट स्कोप तक पहुंच होती है"।
पीटर क्रस

2
बेहतर Unserstanding के लिए इस लिंक का संदर्भ लें javascript.info/tutorial/advanced-functions
सौरभ आहूजा

35
में ES6 , एक छोटी सी समाधान चर घोषित करने के लिए है मैं साथ देना है, जो लूप के शरीर के दायरे वाला।
टॉमस निकोडिम

जवाबों:


2146

खैर, समस्या यह है कि 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++) {
  // and now let's run each one to see
  funcs[j]();
}

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


ES5.1 समाधान: 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) नए letऔर constकीवर्ड्स का परिचय देता है, जिन्हें अलग-अलग varवेरिएबल की तुलना में अलग-अलग तरीके से स्कैन किया जाता है। उदाहरण के लिए, एक- letइंडेड इंडेक्स वाले लूप में, लूप के माध्यम से प्रत्येक पुनरावृत्ति में iलूप स्कोप के साथ एक नया चर होगा, इसलिए आपका कोड आपकी अपेक्षा के अनुरूप काम करेगा। कई संसाधन हैं, लेकिन मैं जानकारी के एक महान स्रोत के रूप में 2 है ब्लॉक-स्कोपिंग पोस्ट की सिफारिश करूंगा

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

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


7
function createfunc(i) { return function() { console.log("My value: " + i); }; }अभी भी बंद नहीं है क्योंकि यह चर का उपयोग करता है i?
ア ッ ク ア

55
दुर्भाग्य से, यह उत्तर पुराना है और किसी को भी नीचे सही उत्तर नहीं दिखाई देगा - Function.bind()निश्चित रूप से अब तक उपयोग करना बेहतर है, देखें stackoverflow.com/a/19323214/785541
व्लादिमीर पलंत

81
@Wladimir: आपका सुझाव है कि .bind()है "सही जवाब" सही नहीं है। प्रत्येक का अपना स्थान है। साथ .bind()आप बाध्यकारी बिना तर्क को नहीं बांध सकता thisमूल्य। इसके अलावा, आपको iकॉल के बीच इसे बदलने की क्षमता के बिना तर्क की एक प्रति मिलती है , जिसकी कभी-कभी आवश्यकता होती है। इसलिए वे काफी अलग निर्माण हैं, यह उल्लेख नहीं करने के लिए कि .bind()कार्यान्वयन ऐतिहासिक रूप से धीमा रहा है। निश्चित रूप से सरल उदाहरण में या तो काम करेगा, लेकिन समझने के लिए क्लोजर एक महत्वपूर्ण अवधारणा है और यही सवाल था।
कुकी राक्षस

8
कृपया इन फ़ॉर-रिटर्न फ़ंक्शन हैक का उपयोग करना बंद करें, इसके बजाय [] .forEach या [] .map का उपयोग करें क्योंकि वे समान स्कोप चर का पुन: उपयोग करने से बचते हैं।
ईसाई लैंडग्रेन

32
@ChristianLandgren: यह केवल तभी उपयोगी है जब आप एक ऐरे की पुनरावृति कर रहे हों। ये तकनीक "हैक" नहीं हैं। वे आवश्यक ज्ञान हैं।

379

प्रयत्न:

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 @ ऑस्ट के अधिक हालिया उत्तर अब इस तरह का काम करने का सबसे अच्छा तरीका है। अंडरस्कोर है भी नहीं है लो-डैश / _.partialजब आप की जरूरत है या के साथ गड़बड़ करने के लिए चाहते नहीं है bindकी thisArg


2
के बारे में कोई स्पष्टीकरण }(i));?
aswzen

3
@aswzen मुझे लगता है कि यह फ़ंक्शन iके तर्क के रूप में गुजरता है index
जेट ब्लू

यह वास्तव में स्थानीय चर सूचकांक बना रहा है।
अभिषेक सिंह

1
तुरंत समारोह अभिव्यक्ति, उर्फ ​​IIFE आह्वान। (i) अनाम फ़ंक्शन एक्सप्रेशन का तर्क है जिसे तुरंत लागू किया जाता है और इंडेक्स i से सेट हो जाता है।
अंडे

348

एक अन्य तरीका जिसका उल्लेख अभी तक नहीं किया गया है वह है इसका उपयोग 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]();
}


यह वही है जो मैं इन दिनों भी करता हूं, मुझे लो-डैश / अंडरस्कोर भी पसंद है_.partial
ब्योर्न

17
.bind()ईसीएमएस्क्रिप्ट 6 सुविधाओं के साथ बड़े पैमाने पर अप्रचलित होगा। इसके अलावा, यह वास्तव में प्रति पुनरावृत्ति दो कार्य करता है। पहले अनाम, फिर एक द्वारा उत्पन्न .bind()। बेहतर उपयोग यह लूप के बाहर बनाने के लिए होगा, फिर .bind()इसे अंदर।

5
@squint @mekdev - आप दोनों सही हैं। मेरा प्रारंभिक उदाहरण प्रदर्शित करने के लिए जल्दी से लिखा गया था कि bindइसका उपयोग कैसे किया जाता है। मैंने आपके सुझावों के अनुसार एक और उदाहरण जोड़ा है।
ऑस्ट

5
मुझे लगता है कि दो हे (n) छोरों पर कम्प्यूटेशन बर्बाद करने के बजाय, बस (var i = 0; i <3; i ++) {log.call (यह, i); }
user2290820

1
.bind () स्वीकृत उत्तर के साथ प्लस फ़िडल्स का सुझाव देता है this

269

एक इंडेक्स वेरिएबल को संलग्न करने का सबसे सरल और सबसे आसान तरीका अभिव्यक्ति का उपयोग करना :

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);

}

यह पुनरावृत्ति iको अनाम फ़ंक्शन में भेजता है जिसके रूप में हम परिभाषित करते हैं index। यह एक बंद बनाता है, जहां चर iIIFE के भीतर किसी भी अतुल्यकालिक कार्यक्षमता में बाद के उपयोग के लिए सहेजा जाता है।


10
आगे कोड पठनीयता के लिए और भ्रम की स्थिति से बचने के लिए iक्या है, मैं फ़ंक्शन पैरामीटर का नाम बदल दूंगा index
काइल फाल्कनर

5
मूल प्रश्न में वर्णित सरणी फ़न को परिभाषित करने के लिए आप इस तकनीक का उपयोग कैसे करेंगे ?
निको

@ निको मूल प्रश्न में दिखाया गया है, सिवाय इसके कि आप indexइसके बजाय उपयोग करेंगे i
JLRishe

@JLRishevar funcs = {}; for (var i = 0; i < 3; i++) { funcs[i] = (function(index) { return function() {console.log('iterator: ' + index);}; })(i); }; for (var j = 0; j < 3; j++) { funcs[j](); }
निको

1
@ एनपीओ ओपी के विशेष मामले में, वे केवल संख्याओं पर पुनरावृत्ति कर रहे हैं, इसलिए यह एक महान मामला नहीं होगा .forEach(), लेकिन बहुत समय, जब कोई एक सरणी के साथ शुरू हो रहा है, forEach()एक अच्छा विकल्प है, जैसे:var nums [4, 6, 7]; var funcs = {}; nums.forEach(function (num, i) { funcs[i] = function () { console.log(num); }; });
JLRishe

163

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

तो जैसा कि कई अन्य ने उल्लेख किया है, समस्या यह है कि आंतरिक फ़ंक्शन समान 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 :

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

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

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

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;
}

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

बंद

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

अपडेट करें

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]();
}

देखो अब कितना आसान है! अधिक जानकारी के लिए इस उत्तर को देखें , जो मेरी जानकारी के आधार पर है।


मुझे पसंद है कि आपने IIFE को कैसे समझाया। मैं उसी की तलाश में था। धन्यवाद।
CapturedTree

4
जावास्क्रिप्ट letऔर constकीवर्ड्स का उपयोग करते हुए अब जावास्क्रिप्ट में ब्लॉक स्कूपिंग जैसी चीज है । यदि इस उत्तर को शामिल करने के लिए विस्तार किया गया, तो यह मेरी राय में विश्व स्तर पर बहुत उपयोगी होगा।

@TinyGiant यकीन है कि, मैं के बारे में कुछ जानकारी जोड़ी letऔर एक और अधिक पूर्ण विवरण जुड़ा हुआ है
woojoo666

@ woojoo666 क्या आपका जवाब भी दो वैकल्पिक URL के लिए लूप में कॉल करने के लिए काम कर सकता है i=0; while(i < 100) { setTimeout(function(){ window.open("https://www.bbc.com","_self") }, 3000); setTimeout(function(){ window.open("https://www.cnn.com","_self") }, 3000); i++ }:? (विंडो को हटा सकते हैं।) () गेटेलीमेंटबीड के साथ ......)
बारे में अखरोट

@nuttyaboutnatty को इस तरह के देर से जवाब के लिए खेद है। ऐसा नहीं लगता कि आपके उदाहरण में कोड पहले से ही काम कर रहा है। आप iअपने टाइमआउट कार्यों में उपयोग नहीं कर रहे हैं , इसलिए आपको एक बंद करने की आवश्यकता नहीं है
woojoo666

151

ES6 के साथ अब व्यापक रूप से समर्थित है, इस सवाल का सबसे अच्छा जवाब बदल गया है। ES6 प्रदान करता है letऔरconst इस सटीक परिस्थिति के लिए कीवर्ड कीवर्ड है। क्लोजर के साथ खिलवाड़ करने के बजाय, हम सिर्फ letलूप स्कोप वैरिएबल को इस तरह सेट करने के लिए उपयोग कर सकते हैं :

var funcs = [];

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

valफिर उस ऑब्जेक्ट को इंगित करेगा जो लूप के उस विशेष मोड़ के लिए विशिष्ट है, और अतिरिक्त क्लोजर नोटेशन के बिना सही मान लौटाएगा। यह स्पष्ट रूप से इस समस्या को काफी सरल करता है।

constletअतिरिक्त प्रतिबंध के साथ समान है कि चर नाम प्रारंभिक असाइनमेंट के बाद एक नए संदर्भ में पलटाव नहीं किया जा सकता है।

ब्राउज़र का समर्थन अब उन ब्राउज़र के नवीनतम संस्करणों को लक्षित करने के लिए यहाँ है। const/ letवर्तमान में नवीनतम फ़ायरफ़ॉक्स, सफारी, एज और क्रोम में समर्थित हैं। यह नोड में भी समर्थित है, और आप इसे बाबेल जैसे उपकरण का लाभ उठाकर कहीं भी उपयोग कर सकते हैं। आप यहां एक कार्यशील उदाहरण देख सकते हैं: http://jsfiddle.net/ben336/rbU4t/2/

डॉक्स यहां:

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


दुर्भाग्य से, 'लेट' अभी भी पूरी तरह से समर्थित नहीं है, खासकर मोबाइल में। developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
मैट एफसी

2
जून '16 के रूप में, लेट आईओएस सफारी, ओपेरा मिनी और सफारी 9. को छोड़कर सभी प्रमुख ब्राउज़र संस्करणों सदाबहार ब्राउज़र इसका समर्थन में समर्थित है। बैबेल उच्च सटीकता मोड पर स्विच किए बिना अपेक्षित व्यवहार रखने के लिए इसे सही तरीके से ट्रांसपाइल करेगा।
डैन पेंट्री

@DanPantry एक अद्यतन के लिए समय के बारे में हाँ :) अद्यतन करने के लिए चीजों की वर्तमान स्थिति को बेहतर ढंग से प्रतिबिंबित करता है, जिसमें कास्ट, डॉक्टर लिंक और बेहतर संगतता जानकारी को शामिल करना शामिल है।
बेन मैककॉर्मिक

क्या ऐसा नहीं है कि हम अपने कोड को ट्रांसपाइल करने के लिए बैबल का उपयोग करते हैं ताकि ES6 / 7 का समर्थन न करने वाले ब्राउज़र समझ सकें कि क्या हो रहा है?
पिक्सेल 67

87

यह कहने का एक और तरीका यह है कि iआपके फ़ंक्शन में फ़ंक्शन फ़ंक्शन को निष्पादित करने के समय बाध्य है, फ़ंक्शन बनाने का समय नहीं है।

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

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

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


71

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

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

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]();
}


@nickf कौन सा ब्राउज़र? जैसा कि मैंने कहा, इसमें संगतता मुद्दे हैं, इसके साथ मेरा मतलब है कि गंभीर संगतता मुद्दे हैं, जैसे मुझे नहीं लगता कि IE में समर्थित है।
इगलासियस

1
@nickf हाँ, इस संदर्भ की जाँच करें: developer.mozilla.org/En/New_in_JavaScript_1.7 ... लेट डेफिनेशन सेक्शन की जाँच करें, एक लूप के अंदर एक ऑनक्लिक उदाहरण है
eglasius

2
@nickf हम्म, वास्तव में आपको स्पष्ट रूप से संस्करण को निर्दिष्ट करना होगा: <स्क्रिप्ट प्रकार = "अनुप्रयोग / जावास्क्रिप्ट; संस्करण = 1.7" /> ... मैं वास्तव में IE प्रतिबंध के कारण कहीं भी इसका इस्तेमाल नहीं किया है, यह अभी नहीं है व्यावहारिक :(
एलगैसियस

आप यहाँ विभिन्न संस्करणों के लिए ब्राउज़र समर्थन देख सकते हैं es.wikipedia.org/wiki/Javascript
eglasius


59

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

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

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


धन्यवाद और आपका समाधान काम करता है। लेकिन मैं पूछना चाहता हूं कि यह क्यों काम करता है, लेकिन varलाइन और लाइन को स्वैप करने से returnकाम नहीं चलेगा? धन्यवाद!
मध्याह्न

@midnite यदि आपने स्वैप किया है varऔर returnतब आंतरिक फ़ंक्शन को वापस करने से पहले चर को असाइन नहीं किया जाएगा।
बन्न

53

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

एक फ़ंक्शन एक नए वातावरण को परिभाषित करता है

विचार करें:

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}एक नई वस्तु में परिणाम बनाया जा रहा है। साथ ही, 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);
  }
}

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


छोटा स्पष्टीकरण: छोरों में क्लोजर के पहले उदाहरण में, काउंटरर्स [0] और काउंटरर्स [1] प्रदर्शन कारणों के कारण स्वतंत्र नहीं हैं। इसका कारण यह है कि var obj = {counter: 0};किसी भी कोड को निष्पादित करने से पहले मूल्यांकन किया जाता है: MDN var : var घोषणाएं, जहां भी वे होते हैं, किसी भी कोड को निष्पादित करने से पहले संसाधित किया जाता है।
चारदिमोस

50

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

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

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]();
}

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

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


2
एक बार किताब में कुछ ऐसा ही पढ़ें। मुझे यह पसंद है, इसलिए भी, क्योंकि आपको अपना मौजूदा कोड (जितना हो सके) स्पर्श नहीं करना है और यह स्पष्ट हो जाता है कि आपने ऐसा क्यों किया है, एक बार जब आप सेल्फ-कॉलिंग फ़ंक्शन पैटर्न सीख गए होते हैं: उस चर को नए बनाए गए जाल में फंसाने के लिए गुंजाइश।
डैनमैन

1
@ डनमैन धन्यवाद। ब्लॉक स्तर के वैरिएबल स्कोप की जावास्क्रिप्ट की कमी से निपटने के लिए सेल्फ कॉलिंग अनाम फ़ंक्शन बहुत अच्छा तरीका है।
केमल दा जूल

3
स्व-कॉलिंग, या स्व-आह्वान इस तकनीक के लिए उपयुक्त शब्द नहीं है, IIFE (तत्काल-इनवॉल्ड फंक्शन एक्सप्रेशन) अधिक सटीक है। Ref: benalman.com/news/2010/11/…
jherax

31

इस छोटे से एक की कोशिश करो

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

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


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

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

http://jsfiddle.net/7P6EN/


1
आपका समाधान सही आउटपुट के लिए लगता है, लेकिन यह अनावश्यक रूप से फ़ंक्शन का उपयोग करता है, तो आउटपुट को केवल कंसोल.लॉग क्यों नहीं? मूल प्रश्न अनाम कार्यों के निर्माण के बारे में है जिनके पास एक ही बंद है। समस्या यह थी, क्योंकि उनके पास एक ही बंद है, उनमें से प्रत्येक के लिए i का मूल्य समान है। मुझे आशा है कि आपको मिल गया होगा।
केमल दा

30

यहां एक सरल समाधान है जो उपयोग करता है 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

27

ओपी द्वारा दिखाए गए कोड के साथ मुख्य मुद्दा यह है कि 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]()प्रयोग किया जाता है, और iमूल्यांकन किया जाता है, यह 3 है - हर बार।

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

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

jsFiddle Demo

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

23

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

var funcs = []

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

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

ऊपर दिए गए ऐरे में प्रत्येक फ़ंक्शन वैश्विक स्कोप (वैश्विक, केवल इसलिए बंद हो जाता है क्योंकि ऐसा हो सकता है कि वे स्कोप घोषित हो)।

बाद में उन कार्यों को iवैश्विक दायरे में सबसे वर्तमान मूल्य को लॉग इन किया जाता है । वह जादू, और हताशा, बंद होने की है।

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

लूप के चलने पर हर बार एक नया स्कोप बनाकर इसका उपयोग letकरने के बजाय , प्रत्येक फ़ंक्शन को बंद करने के लिए एक अलग स्कोप बनाते हैं। विभिन्न कार्यों के साथ विभिन्न अन्य तकनीकें समान कार्य करती हैं।varfor

var funcs = []

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

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

( letवैरिएबल ब्लॉक को स्कोप किया जाता है। ब्लॉक को बेली ब्रेसेस द्वारा निरूपित किया जाता है, लेकिन लूप के iमामले में इनिशियलाइज़ वैरिएबल, हमारे मामले में, ब्रेसेस में घोषित किया जाता है।)


1
मैंने इस अवधारणा को समझने के लिए तब तक संघर्ष किया जब तक मैंने इस उत्तर को नहीं पढ़ा। यह वास्तव में महत्वपूर्ण बिंदु पर छूता है - के मूल्य iको वैश्विक दायरे में सेट किया जा रहा है। जब forलूप चलना समाप्त हो जाता है, तो वैश्विक मान iअब 3 है। इसलिए, जब भी उस फ़ंक्शन को ऐरे में इनवॉइस (उपयोग करना, कहना funcs[j]) किया जाता है, तो iउस फ़ंक्शन में ग्लोबल iवैरिएबल (जो कि 3 है) को संदर्भित करता है ।
मोदर्मो

13

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

  • प्रत्येक फ़ंक्शन की परिभाषा एक ऐसा दायरा बनाती है जिसमें 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

बाहरी कार्य में इसे लपेटकर या तो स्पष्ट रूप से एक सहायक फ़ंक्शन को परिभाषित करें जैसे कि हार्टो ने किया था या ब्योर्न की तरह एक अनाम फ़ंक्शन का उपयोग करें :

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जब आंतरिक निष्पादित किया जाता है तो यह मूल्य का उपयोग नहीं करेगा ।

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


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

13

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
}

ओपी के प्रश्न में कोड को letइसके बजाय बदल दिया गया है var


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

10

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


3
.forEach()यदि आप वास्तव में कुछ भी मैप नहीं कर रहे हैं तो एक बेहतर विकल्प है, और डेरिल ने सुझाव दिया कि आपके पोस्ट किए जाने के 7 महीने पहले, इसलिए आश्चर्यचकित होने की कोई बात नहीं है।
JLRishe

यह प्रश्न किसी सरणी पर लूप के बारे में नहीं है
jherax

खैर, वह कार्यों की एक सरणी बनाना चाहता है, यह उदाहरण दिखाता है कि वैश्विक चर को शामिल किए बिना ऐसा कैसे करें।
क्रिश्चियन लैंडग्रेन

9

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

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>


8

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


8

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

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]();                        
    }

8

क्लोजर संरचना का उपयोग करें , यह लूप के लिए आपके अतिरिक्त को कम करेगा। आप इसे लूप के लिए एकल में कर सकते हैं:

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

7

हम जांच करेंगे, जब आप घोषणा करते हैं varऔर let एक - एक करके वास्तव में क्या होता है ।

Case1 : का उपयोग कर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 कार्यों का व्यय करें। आपको एक .Expand नामक एक संपत्ति दिखाई देगी । आपको एक सरणी ऑब्जेक्ट दिखाई देगी , उस एक का विस्तार करें। आपको उस ऑब्जेक्ट में घोषित एक संपत्ति मिलेगी , जिसका मूल्य 3 है।[[Scopes]]"Global"'i'

यहां छवि विवरण दर्ज करें

यहां छवि विवरण दर्ज करें

निष्कर्ष:

  1. जब आप 'var'किसी फ़ंक्शन के बाहर एक वैरिएबल की घोषणा करते हैं, तो यह ग्लोबल वेरिएबल बन जाता है (आप टाइप करके iया window.iकंसोल विंडो में चेक कर सकते हैं । यह 3 वापस आ जाएगा)।
  2. आपके द्वारा घोषित सबसे बड़ा फ़ंक्शन तब तक कॉल नहीं करेगा और फ़ंक्शन के अंदर मूल्य की जांच करेगा जब तक कि आप फ़ंक्शन को लागू नहीं करते हैं।
  3. जब आप फ़ंक्शन को लागू करते हैं, तो console.log("My value: " + i)उसके Globalऑब्जेक्ट से मूल्य लेता है और परिणाम प्रदर्शित करता है।

CASE2: let का उपयोग करना

अब के '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 पर जाएं


4

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

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

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

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

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


1
मुझे डाउन वोट का स्पष्टीकरण पसंद आएगा। कोड हाथ में समस्या हल करता है। यह जानना मूल्यवान होगा कि संभावित रूप से कोड में सुधार कैसे किया जा सकता है
रुई एफएस

1
क्या है Query.range(0,3)? यह इस प्रश्न के टैग का हिस्सा नहीं है। इसके अलावा, यदि आप किसी तीसरे पक्ष के पुस्तकालय का उपयोग करते हैं, तो आप दस्तावेज़ का लिंक प्रदान कर सकते हैं।
jaxax

1
@jherax उन या स्पष्ट सुधार पाठ्यक्रम हैं। टिप्पणी के लिए धन्यवाद। मैं शपथ ले सकता था कि पहले से ही एक कड़ी थी। बाहर के साथ कि पोस्ट बहुत बेकार था मुझे लगता है :)। इसे रखने का मेरा प्रारंभिक विचार था क्योंकि मैं अपने स्वयं के पुस्तकालय के उपयोग को आगे बढ़ाने की कोशिश नहीं कर रहा था, लेकिन घोषणात्मक विचार भी। हालाँकि, मैं पूरी तरह से सहमत हूँ कि लिंक वहाँ होना चाहिए
Rune FS

4

मैं forEachफंक्शन का उपयोग करना पसंद करता हूं , जिसका छद्म रेंज बनाने के साथ खुद का बंद होना है:

var funcs = [];

new Array(3).fill(0).forEach(function (_, i) { // creating a range
    funcs[i] = function() {            
        // now i is safely incapsulated 
        console.log("My value: " + i);
    };
});

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

कि अन्य भाषाओं में पर्वतमाला की तुलना में बदसूरत दिखता है, लेकिन अन्य समाधानों की तुलना में IMHO कम राक्षसी है।


यह क्या पसंद है? यह कुछ अन्य उत्तर के जवाब में एक टिप्पणी लगती है। यह बिल्कुल भी वास्तविक प्रश्न को संबोधित नहीं करता है (क्योंकि आप किसी फ़ंक्शन को असाइन नहीं कर रहे हैं, बाद में, कहीं भी बुलाया जाएगा)।
क्वेंटिन

यह उल्लेखित मुद्दे से बिल्कुल संबंधित है: बंद समस्याओं के बिना सुरक्षित रूप से पुनरावृति कैसे करें
रक्स वूटर

अब यह स्वीकृत उत्तर से काफी अलग नहीं है।
क्वेंटिन

नहीं। स्वीकृत उत्तर में "कुछ सरणी" का उपयोग करने का सुझाव दिया गया है, लेकिन हम जवाब में एक सीमा के साथ सौदा करते हैं, यह बिल्कुल अलग चीजें हैं, जो दुर्भाग्य से js में एक अच्छा समाधान नहीं है, इसलिए मेरा उत्तर हल करने की कोशिश कर रहा है एक अच्छा और अभ्यास के तरीके में मुद्दा
Rax Wunter

@ क्वेंटिन मैं मिन्यूज़िंग से पहले समाधान की जांच करने की सिफारिश करूंगा
रक्स वंटर

4

और फिर भी एक और समाधान: एक और लूप बनाने के बजाय, केवल 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
}

इसे बांधने से समस्या का हल भी हो जाता है।


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);
  }
}

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


@TinyGiant जिस फ़ंक्शन को लौटाया जा रहा है उसका उदाहरण अभी भी प्रदर्शन के लिए अनुकूलित किया गया है। मैं सभी जावास्क्रिप्ट ब्लॉगर्स की तरह तीर के फंक्शन बैंडवगन पर नहीं कूदूंगा। वे शांत और साफ दिखते हैं लेकिन पूर्वनिर्धारित कार्यों का उपयोग करने के बजाय लेखन कार्यों को इनलाइन को बढ़ावा देते हैं। यह गर्म स्थानों में एक गैर-स्पष्ट जाल हो सकता है। एक और समस्या यह है कि वे सिर्फ सिंथैटिक चीनी नहीं हैं क्योंकि वे अनावश्यक बाइंडिंग को निष्पादित कर रहे हैं और इस प्रकार लपेटन बंद कर रहे हैं।
पावेल

2
भविष्य के पाठकों को चेतावनी: यह उत्तर गलत तरीके से करी शब्द को लागू करता है । "करीयन तब होता है जब आप एक फ़ंक्शन को तोड़ते हैं जो कई तर्कों को कार्यों की एक श्रृंखला में ले जाता है जो तर्कों का हिस्सा होता है।" । यह कोड कुछ भी नहीं करता है। आपके द्वारा यहां किया गया सभी कोड स्वीकार किए गए उत्तर से लिया गया है, कुछ चीजों को इधर-उधर ले जाएं, शैली बदलें और थोड़ा नाम दें, फिर इसे क्यूरिंग कहें, जो यह स्पष्ट रूप से नहीं है।

3

आपका कोड काम नहीं करता है, क्योंकि यह क्या करता है:

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

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


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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.