etc./StackOverFlow

루프 내부의 JavaScript 클로저 – 간단한 실제 예

청렴결백한 만능 재주꾼 2021. 11. 5. 23:09
반응형

질문자 :nickf


 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>

... 또는 비동기 코드(예: Promise 사용):

 // 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 infor of 루프에서도 분명합니다.

 const arr = [1,2,3]; const fns = []; for(var i in arr){ fns.push(() => console.log(`index: ${i}`)); } for(var v of arr){ fns.push(() => console.log(`value: ${v}`)); } for(var f of fns){ f(); }

이 근본적인 문제에 대한 해결책은 무엇입니까?



음, 문제는 i 가 함수 외부의 동일한 변수에 바인딩되어 있다는 것입니다.

ES6 솔루션: let

ECMAScript 6(ES6)에는 var 기반 변수와 범위가 다른 letconst let 기반 인덱스가 있는 루프에서 루프를 반복할 때마다 i 가 있으므로 코드가 예상대로 작동합니다. 많은 리소스가 있지만 2ality의 블록 범위 게시물 을 훌륭한 정보 소스로 추천합니다.

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

그러나 Edge 14 지원 이전의 IE9-IE11 및 Edge let 하지만 위의 오류가 발생합니다( i var 를 사용하는 경우와 같이 3을 기록합니다). Edge 14는 마침내 올바르게 작동합니다.


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() 함수가 유사한 기능을 제공합니다.


클래식 솔루션: 폐쇄

원하는 것은 각 함수 내의 변수를 함수 외부의 변경되지 않는 별도의 값에 바인딩하는 것입니다.

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

JavaScript에는 블록 범위가 없고 함수 범위만 있으므로 새 함수에서 함수 생성을 래핑하여 "i" 값이 의도한 대로 유지되도록 합니다.


harto

노력하다:

 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 사용에 대한 .bind 최근 답변이 지금 이런 종류의 작업을 수행하는 가장 좋은 방법이라고 생각합니다. bindthisArg 가 필요하지 않거나 엉망이 되고 싶을 때 lo-dash/underscore의 _.partial 있습니다.


Bjorn

아직 언급되지 않은 또 다른 방법은 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](); }


Aust

즉시 호출 함수 표현식을 사용하여 인덱스 변수를 묶는 가장 간단하고 읽기 쉬운 방법:

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

이것은 iterator i index 정의한 익명 함수로 보냅니다. 이것은 IIFE 내의 비동기 기능에서 나중에 사용하기 위해 i 가 저장되는 클로저를 생성합니다.


neurosnap

파티에 조금 늦었지만 오늘 이 문제를 조사하다가 많은 답변이 Javascript가 범위를 처리하는 방법을 완전히 다루지 않는다는 것을 알게 되었습니다.

다른 많은 사람들이 언급했듯이 문제는 내부 함수가 동일한 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에서 :

중요: JavaScript에는 블록 범위가 없습니다. 블록과 함께 도입된 변수는 포함하는 함수 또는 스크립트로 범위가 지정되며 설정의 효과는 블록 자체를 넘어 지속됩니다. 즉, 블록 문은 범위를 도입하지 않습니다. "독립 실행형" 블록이 유효한 구문이지만 JavaScript에서 독립 실행형 블록을 사용하고 싶지는 않습니다. C 또는 Java에서 이러한 블록과 같은 작업을 수행한다고 생각하는 경우 해당 블록이 수행하는 작업을 수행하지 않기 때문입니다.

강조를 위해 반복:

JavaScript에는 블록 범위가 없습니다. 블록과 함께 도입된 변수는 포함하는 함수 또는 스크립트로 범위가 지정됩니다.

각 반복에서 선언하기 전에 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는 경고도 발생시키지 않습니다. 이것이 바로 이 문제를 해결하는 가장 좋은 방법이 클로저를 활용하는 것인 이유이기도 합니다. 이는 본질적으로 Javascript에서 내부 범위가 외부 범위를 "묶기" 때문에 내부 함수가 외부 변수에 액세스할 수 있다는 아이디어입니다.

폐쇄

이것은 또한 내부 함수가 외부 변수를 "유지"하고 외부 함수가 반환되더라도 활성 상태를 유지한다는 것을 의미합니다. 이것을 활용하기 위해 우리는 순전히 새 범위를 만들고, 새 범위에서 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](); }

이제 얼마나 쉬운지 보세요! 자세한 내용은 내 정보의 기반이 되는 이 답변 을 참조하십시오.


woojoo666

이제 ES6이 널리 지원되면서 이 질문에 대한 최상의 답변이 변경되었습니다. ES6은 이 정확한 상황에 대해 letconst 클로저를 어지럽히는 대신 let 을 사용하여 다음과 같이 루프 범위 변수를 설정할 수 있습니다.

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

val 은 루프의 특정 회전에 특정한 객체를 가리키고 추가 클로저 표기 없이 올바른 값을 반환합니다. 이것은 분명히 이 문제를 상당히 단순화합니다.

const 는 초기 할당 후에 변수 이름을 새 참조로 리바인드할 수 없다는 추가 제한이 있는 let

이제 최신 버전의 브라우저를 대상으로 하는 브라우저 지원이 제공됩니다. const / let 은 현재 최신 Firefox, Safari, Edge 및 Chrome에서 지원됩니다. Node에서도 지원되며 Babel과 같은 빌드 도구를 활용하여 어디서나 사용할 수 있습니다. 여기에서 작업 예제를 볼 수 있습니다: http://jsfiddle.net/ben336/rbU4t/2/

문서:

그러나 Edge 14 지원 이전의 IE9-IE11 및 Edge let 하지만 위의 오류가 발생합니다( i var 를 사용하는 경우와 같이 3을 기록합니다). Edge 14는 마침내 올바르게 작동합니다.


Ben McCormick

그것을 말하는 또 다른 방법은 i 가 함수를 생성할 때가 아니라 함수를 실행할 때 바인딩된다는 것입니다.

클로저를 생성할 때 i 는 클로저를 생성할 때의 복사본이 아니라 외부 범위에 정의된 변수에 대한 참조입니다. 실행 시 평가됩니다.

다른 답변의 대부분은 값을 변경하지 않는 다른 변수를 만들어 해결하는 방법을 제공합니다.

명확성을 위해 설명을 추가할 것이라고 생각했습니다. 솔루션의 경우 개인적으로 Harto's가 여기에 있는 답변에서 가장 자명한 방법이기 때문에 저는 Harto's를 사용합니다. 게시된 모든 코드는 작동하지만, 내가 왜 새로운 변수(Freddy 및 1800년대)를 선언하는지 설명하거나 이상한 임베디드 클로저 구문(apphacker)을 갖는 이유를 설명하기 위해 많은 주석을 작성해야 하는 것보다 클로저 팩토리를 선택할 것입니다.


Darren Clark

이해해야 할 것은 자바스크립트에서 변수의 범위가 함수를 기반으로 한다는 것입니다. 이것은 블록 범위가 있는 C#과 다른 중요한 차이점이며 변수를 for 내부에 복사하는 것만으로도 작동합니다.

변수에 이제 함수 범위가 있으므로 apphacker의 답변과 같은 함수 반환을 평가하는 함수로 래핑하면 트릭이 수행됩니다.

var 대신 let 키워드도 있어 차단 범위 규칙을 사용할 수 있습니다. 이 경우 for 내부에 변수를 정의하면 트릭을 수행할 수 있습니다. 즉, let 키워드는 호환성 때문에 실용적인 솔루션이 아닙니다.

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


eglasius

다음은 Bjorn(apphacker)과 유사한 기술의 또 다른 변형으로, 매개변수로 전달하는 대신 함수 내부에 변수 값을 할당할 수 있습니다. 때로는 더 명확할 수 있습니다.

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

어떤 기술을 사용하든 index 변수는 내부 함수의 반환된 복사본에 바인딩된 일종의 정적 변수가 됩니다. 즉, 해당 값의 변경 사항은 호출 간에 유지됩니다. 매우 편리할 수 있습니다.


Boann

이것은 JavaScript에서 클로저를 사용하는 일반적인 실수를 설명합니다.

함수는 새로운 환경을 정의합니다

고려하다:

 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 의 새 복사본도 만들어집니다. 따라서 counter1counter2 는 서로 독립적입니다.

루프의 폐쇄

루프에서 클로저를 사용하는 것은 까다롭습니다.

고려하다:

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

이것은 함수 범위의 지역 변수와 함수 인수 변수가 입력 시 새 복사본으로 할당되기 때문에 작동합니다.


user1724763

가장 간단한 해결책은,

사용하는 대신:

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

"2"를 3번 경고합니다. for 루프에서 생성된 익명 함수는 동일한 클로저를 공유하고 해당 클로저에서 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](); }

이에 대한 아이디어는 for 루프의 전체 본문을 IIFE (Immediately-Invoked Function Expression)로 new_i 하고 new_i 를 매개변수로 i 로 캡처하는 것입니다. 익명 함수는 즉시 실행되기 때문에 익명 함수 내부에 정의된 함수마다 i

이 솔루션은 이 문제로 고통받는 원본 코드를 최소한으로 변경해야 하므로 이러한 문제에 적합한 것으로 보입니다. 사실 이것은 의도된 것이며 전혀 문제가 되지 않아야 합니다!


Kemal Dağ

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

Daryl

이 더 짧은 것을 시도하십시오

  • 배열 없음

  • 추가 for 루프 없음


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

http://jsfiddle.net/7P6EN/


yilmazburk

OP에 표시된 코드의 주요 문제 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 조건이 실패하고 루프가 종료됩니다. 이 시점에서 i3 이므로 funcs[someIndex]() 를 사용하고 i 를 평가하면 매번 3입니다.

이를 극복하려면 i 가 발생했을 때 평가해야 합니다. funcs[i] 의 형태로 이미 발생했습니다(3개의 고유 인덱스가 있음). 이 값을 캡처하는 방법에는 여러 가지가 있습니다. 하나는 이미 여기에 여러 방식으로 표시된 함수에 매개변수로 전달하는 것입니다.

또 다른 옵션은 변수를 닫을 수 있는 함수 개체를 구성하는 것입니다. 그렇게 할 수 있다.

jsFiddle Demo


Community Wiki

JavaScript 함수는 선언 시 액세스할 수 있는 범위를 "닫고" 해당 범위의 변수가 변경되더라도 해당 범위에 대한 액세스를 유지합니다.

 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 의 가장 최신 값을 기록합니다. 그것이 폐쇄의 마법이자 좌절입니다.

"JavaScript 함수는 선언된 범위를 닫고 해당 범위 내의 변수 값이 변경되더라도 해당 범위에 대한 액세스를 유지합니다."

var 대신 let 을 사용 for 루프가 실행될 때마다 새 범위를 만들고 닫을 각 함수에 대해 별도의 범위를 만들어 이 문제를 해결합니다. 다양한 다른 기술은 추가 기능과 동일한 작업을 수행합니다.

 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 은 변수를 블록 범위로 만듭니다. 블록은 중괄호로 표시되지만 for 루프의 경우 초기화 변수인 i 는 이 경우 중괄호로 선언된 것으로 간주됩니다.)


Costa Michailidis

다양한 솔루션을 읽은 후 이러한 솔루션이 작동하는 이유는 범위 체인 개념에 의존하기 때문이라고 덧붙이고 싶습니다. JavaScript가 실행 중에 변수를 해결하는 방법입니다.

  • 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 에서 찾을 때까지 계속 검색합니다.

외부 함수로 래핑하여 harto 와 같은 도우미 함수를 명시적으로 정의 하거나 Bjorn 과 같은 익명 함수를 사용합니다.

 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 됩니다. 이번에는 for 루프에서 3번 실행되는 외부 함수의 범위에서 i i 값이 올바르게 바인딩됩니다. 내부 실행 시 window.i 값을 사용하지 않습니다.

자세한 내용은 여기에서 확인할 수 있습니다.
여기에는 루프에서 클로저를 생성하는 일반적인 실수와 클로저가 필요한 이유 및 성능 고려 사항이 포함됩니다.


wpding

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의 질문에 있는 코드는 var let 으로 바뀝니다.


Prithvi Uppalapati

지역 변수를 (재)사용하는 것을 더 잘 피하기 위해 forEach 함수를 사용하도록 제안한 사람이 아직 없다는 사실에 놀랐습니다. 사실, 나는 이런 이유로 더 이상 for for(var i ...)

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

// map 대신 forEach 를 사용하도록 수정했습니다.


Christian Landgren

이 질문은 실제로 JavaScript의 역사를 보여줍니다! 이제 화살표 함수로 블록 범위 지정을 피하고 Object 메서드를 사용하여 DOM 노드에서 직접 루프를 처리할 수 있습니다.

 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>


sidhuko

원래 예제가 작동하지 않는 이유는 루프에서 만든 모든 클로저가 동일한 프레임을 참조했기 때문입니다. i 변수만 있는 하나의 개체에 3개의 메서드가 있습니다. 그들은 모두 같은 값을 출력했습니다.


jottos

우선 이 코드의 문제점을 이해하십시오.

 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 배열은 초기화되고 func 배열의 크기는 3이 되므로 i = 3, . 이제 funcs[j]() 가 호출되면 이미 3으로 증가된 i

이제 이를 해결하기 위해 많은 옵션이 있습니다. 다음은 그 중 두 가지입니다.

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

Ali Kahoot

클로저 구조를 사용하면 추가 for 루프가 줄어듭니다. 단일 for 루프에서 수행할 수 있습니다.

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

Vikash_Singh

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개의 함수를 확장합니다. [[Scopes]] 라는 속성이 표시됩니다. 해당 속성을 확장합니다. "Global" 이라는 하나의 배열 개체가 표시되고 해당 개체를 확장합니다. 값이 3인 객체에 선언된 속성 'i'

여기에 이미지 설명 입력

여기에 이미지 설명 입력

결론:

  1. 'var' 사용하여 변수를 선언하면 전역 변수가 됩니다( window.i 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를 참조하십시오.


Community Wiki

query-js (*)와 같은 데이터 목록에 선언적 모듈을 사용할 수 있습니다. 이러한 상황에서 저는 개인적으로 선언적 접근 방식이 덜 놀랍습니다.

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

그런 다음 두 번째 루프를 사용하여 예상 결과를 얻거나 할 수 있습니다.

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

(*) 저는 query-js의 저자이므로 그것을 사용하는 데 편향되어 있으므로 선언적 접근 방식에 대해서만 해당 라이브러리에 대한 권장 사항으로 제 말을 사용하지 마십시오. :)


Rune FS

유사 범위를 생성하는 자체 클로저가 있는 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는 다른 솔루션보다 덜 괴물입니다.


Rax Wunter

또 다른 솔루션: 다른 루프를 만드는 대신 반환 함수에 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 }

이것을 바인딩하여 문제도 해결합니다.


pixel 67

ES5까지 이 문제는 closure 를 통해서만 해결할 수 있습니다.

그러나 이제 ES6에는 블록 수준 범위 변수가 있습니다. 먼저 for 루프허용하도록 var 를 변경하면 문제가 해결됩니다.

 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 (var j = 0; j < 3; j++) { funcs[j](); // and now let's run each one to see }


Shivang Gupta

for while 루프에서 이러한 종류의 문제가 발생하는 경우 예를 들면 다음과 같습니다.

 var i = 0; while (i < 5) { setTimeout(function() { console.log(i); }, i * 1000); i++; }

현재 값을 닫는 기술은 약간 다릅니다. while 블록 내부에 const 하여 블록 범위 변수를 선언하고 현재 i 를 할당합니다. 그런 다음 변수가 비동기적으로 사용될 때마다 i 를 새로운 블록 범위 변수로 바꿉니다.

 var i = 0; while (i < 5) { const thisIterationI = i; setTimeout(function() { console.log(thisIterationI); }, i * 1000); i++; }

블록 범위 변수를 지원하지 않는 이전 브라우저의 경우 i 와 함께 호출된 IIFE를 사용할 수 있습니다.

 var i = 0; while (i < 5) { (function(innerI) { setTimeout(function() { console.log(innerI); }, innerI * 1000); })(i); i++; }

호출 할 수있는 비동기 작업이 될 일 경우 setTimeout 위처럼, 당신은 또한 호출 할 수 있습니다 setTimeout 함께 전달 된 함수를 호출 할 수있는 인수를 나타 내기 위해 세 번째 매개 변수 :

 var i = 0; while (i < 5) { setTimeout( (thisIterationI) => { // Callback console.log(thisIterationI); }, i * 1000, // Delay i // Gets passed to the callback; becomes thisIterationI ); i++; }


CertainPerformance

많은 솔루션이 올바른 것처럼 보이지만 여기와 같은 상황에 대한 함수형 프로그래밍 디자인 패턴 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); } }

다양한 브라우저에서 성능 향상을 확인하십시오.


Pawel

코드가 작동하지 않는 이유는 다음과 같습니다.

 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 값을 function 에 저장하고 그 후에야 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 값으로 설정합니다.

이것은 이 문제를 해결하는 여러 가지 방법 중 하나일 뿐입니다.


Buksy

출처 : http:www.stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example

반응형