자바 스크립트 클로저와 익명 함수


562

내 친구와 나는 현재 JS의 폐쇄와 그렇지 않은 것을 논의하고 있습니다. 우리는 그것을 정확하게 이해하고 싶어합니다.

이 예제를 보자. 카운트 루프가 있고 지연된 콘솔에서 카운터 변수를 인쇄하려고합니다. 따라서 카운터 변수의 값을 캡처하여 클로저 를 사용 setTimeout하여 N의 N 배를 N으로 인쇄하지 않도록합니다.

없는 잘못된 솔루션 폐쇄 또는 근처에 아무것도 폐쇄가 될 것이다 :

for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}

물론 i루프 후 값의 10 배 , 즉 10을 인쇄합니다.

그의 시도는 다음과 같습니다.

for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i;
        setTimeout(function(){
            console.log(i2);
        }, 1000)
    })();
}

예상대로 0에서 9까지 인쇄합니다.

나는 그가 캡쳐 를 위해 클로저 를 사용하고 있지 않다고 말 i했지만 그는 자신을 주장한다. 나는 for 루프 본문을 다른 루프 안에 넣고 (익명 함수를에 전달 ) 10 번 10을 다시 인쇄 하여 클로저 를 사용하지 않는다는 것을 증명 했습니다. 나는 그의 기능을 저장하는 경우 동일하게 적용 하고 실행 한 후 내 인수가 있다는 것입니다 그래서도 10 배 (10)를 인쇄, 루프 그가 정말하지 않습니다 캡처 의 값을 자신의 버전 만들기, 하지 클로저를.setTimeoutsetTimeoutvari

나의 시도는 :

for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2);
        }
    })(i), 1000);
}

그래서 i( i2클로저 내에서 명명 된) 캡처 하지만 이제 다른 함수를 반환 하고 이것을 전달합니다. 필자의 경우 setTimeout에 전달 된 함수가 실제로 캡처합니다 i.

이제 누가 클로저를 사용하고 있고 누가 사용하지 않습니까?

두 솔루션 모두 콘솔에서 0에서 9까지 지연되어 원래 문제를 해결하지만이 두 솔루션 중 클로저사용 하여이를 수행하는 솔루션을 이해하고 싶습니다 .



1
@leemes : 두 번째 링크는 내 닌자 편집을 참조하십시오.
Blender

2
우리는 방금 거래를했습니다. 옳은 사람은이 질문과 관련된 SO 포인트를 얻을 것입니다.
brillout

1
@leemes-둘 다 클로저를 사용하고 있습니다. 둘 다 두 가지 기능, 즉 외부 기능과 내부 기능을 만들었습니다. 그리고 당신의 내부 기능은 모두 폐쇄입니다. 모든 함수는 람다 ( 익명 함수 )입니다. 자세한 내용은 내 대답을 읽으십시오.
Aadit M Shah

1
@blesh-수정 된 클로저가 무엇인지 전혀 모른다. 귀하의 링크가 C # 코드를 가리키는 것으로 보입니다. JavaScript에서 수정 된 클로저가 지원됩니까?
Aadit M Shah

답변:


650

편집자 주 : JavaScript의 모든 기능은이 게시물 에서 설명한 폐쇄 입니다. 그러나 우리는 이론적 관점에서 흥미로운 이러한 기능의 하위 집합을 식별하는 데에만 관심 이 있습니다. 따라서, 단어 클로저에 대한 언급은 달리 언급되지 않는 한 이러한 기능의 서브 세트를 지칭 할 것이다.

클로저에 대한 간단한 설명 :

  1. 기능을 수행하십시오. F라고 부르겠습니다.
  2. F의 모든 변수를 나열하십시오.
  3. 변수는 두 가지 유형일 수 있습니다.
    1. 지역 변수 (바운드 변수)
    2. 비 국소 변수 (자유 변수)
  4. F에 자유 변수가 없으면 클로저가 될 수 없습니다.
  5. F에 자유 변수 ( F 상위 범위에 정의 된 )가있는 경우 :
    1. 되는 F의 부모 범위가 있어야합니다 무료 변수가 바인딩됩니다.
    2. F가 해당 상위 범위 외부에서 참조 되면 해당 자유 변수에 대한 클로저가됩니다 .
    3. 자유 변수를 클로저 F의 상위 값이라고합니다.

이제 이것을 사용하여 클로저를 사용하는 사람과 그렇지 않은 사람을 알아 봅시다 (설명을 위해 함수 이름을 지정했습니다).

사례 1 : 친구의 프로그램

for (var i = 0; i < 10; i++) {
    (function f() {
        var i2 = i;
        setTimeout(function g() {
            console.log(i2);
        }, 1000);
    })();
}

위의 프로그램에서이 두 가지 기능은 다음과 같습니다 fg. 그들이 폐쇄인지 보자.

의 경우 f:

  1. 변수를 나열하십시오.
    1. i2A는 로컬 변수.
    2. iA는 무료 변수.
    3. setTimeoutA는 무료 변수.
    4. gA는 로컬 변수.
    5. consoleA는 무료 변수.
  2. 각 자유 변수가 바인딩 된 상위 범위를 찾으십시오.
    1. i되는 바인딩 전역에.
    2. setTimeout되는 바인딩 전역에.
    3. console되는 바인딩 전역에.
  3. 어떤 범위에서 함수가 참조 됩니까? 전역 범위 .
    1. 따라서 의해 폐쇄i 되지 않습니다 .f
    2. 따라서 의해 폐쇄setTimeout 되지 않습니다 .f
    3. 따라서 의해 폐쇄console 되지 않습니다 .f

따라서 기능 f은 폐쇄가 아닙니다.

의 경우 g:

  1. 변수를 나열하십시오.
    1. consoleA는 무료 변수.
    2. i2A는 무료 변수.
  2. 각 자유 변수가 바인딩 된 상위 범위를 찾으십시오.
    1. console되는 바인딩 전역에.
    2. i2되는 바인딩 의 범위에 f.
  3. 어떤 범위에서 함수가 참조 됩니까? 의 범위setTimeout .
    1. 따라서 의해 폐쇄console 되지 않습니다 .g
    2. 그러므로 i2되어 닫혀 의해 g.

따라서 함수는 g자유 변수에 대한 폐쇄입니다 i2(대한 upvalue이다 g) 경우 가있어 참조 내에서가 setTimeout.

당신에게 나쁜 점 : 친구가 폐쇄를 사용하고 있습니다. 내부 기능은 폐쇄입니다.

사례 2 : 프로그램

for (var i = 0; i < 10; i++) {
    setTimeout((function f(i2) {
        return function g() {
            console.log(i2);
        };
    })(i), 1000);
}

위의 프로그램에서이 두 가지 기능은 다음과 같습니다 fg. 그들이 폐쇄인지 보자.

의 경우 f:

  1. 변수를 나열하십시오.
    1. i2A는 로컬 변수.
    2. gA는 로컬 변수.
    3. consoleA는 무료 변수.
  2. 각 자유 변수가 바인딩 된 상위 범위를 찾으십시오.
    1. console되는 바인딩 전역에.
  3. 어떤 범위에서 함수가 참조 됩니까? 전역 범위 .
    1. 따라서 의해 폐쇄console 되지 않습니다 .f

따라서 기능 f은 폐쇄가 아닙니다.

의 경우 g:

  1. 변수를 나열하십시오.
    1. consoleA는 무료 변수.
    2. i2A는 무료 변수.
  2. 각 자유 변수가 바인딩 된 상위 범위를 찾으십시오.
    1. console되는 바인딩 전역에.
    2. i2되는 바인딩 의 범위에 f.
  3. 어떤 범위에서 함수가 참조 됩니까? 의 범위setTimeout .
    1. 따라서 의해 폐쇄console 되지 않습니다 .g
    2. 그러므로 i2되어 닫혀 의해 g.

따라서 함수는 g자유 변수에 대한 폐쇄입니다 i2(대한 upvalue이다 g) 경우 가있어 참조 내에서가 setTimeout.

당신을 위해 좋은 : 당신은 폐쇄를 사용하고 있습니다. 내부 기능은 폐쇄입니다.

그래서 당신과 당신의 친구 모두 폐쇄를 사용하고 있습니다. 말다툼을 멈추십시오. 클로저의 개념과 두 사람 모두를 식별하는 방법을 명확하게 정리했으면합니다.

편집 : 모든 함수가 왜 폐쇄되는지에 대한 간단한 설명 (크레딧 @ 피터) :

먼저 다음 프로그램을 고려해 봅시다 ( 제어입니다 ).

lexicalScope();

function lexicalScope() {
    var message = "This is the control. You should be able to see this message being alerted.";

    regularFunction();

    function regularFunction() {
        alert(eval("message"));
    }
}

  1. 우리는 모두 알고 lexicalScoperegularFunction폐쇄하지 않습니다 위의 정의에서 .
  2. 프로그램을 실행할 때 클로저가 아니기 때문에 경고를받을 것으로 예상 message 됩니다 (예 : 부모 범위의 모든 변수에 액세스 할 수 있음 ). regularFunctionmessage
  3. 우리가 프로그램을 실행할 때 실제로 경고 되는 것을 관찰 합니다 message.

다음 프로그램을 고려해 봅시다 ( 대안입니다 ).

var closureFunction = lexicalScope();

closureFunction();

function lexicalScope() {
    var message = "This is the alternative. If you see this message being alerted then in means that every function in JavaScript is a closure.";

    return function closureFunction() {
        alert(eval("message"));
    };
}

  1. 우리는 단지 위의 정의에서closureFunction 폐쇄 것임을 알고 있습니다.
  2. 우리가 프로그램을 실행하면 우리는 기대 message 경고를 할 수 없습니다 때문에 closureFunction (즉, 그것은 단지 모든에 액세스 할 수있는 폐쇄입니다 비 지역 변수함수가 작성 될 때 ( 이 답변을 참조 -이 포함되지 않습니다이) message).
  3. 프로그램을 실행할 때 실제로 경고 하고 있음 을 관찰 합니다 message.

우리는 이것으로부터 무엇을 추론합니까?

  1. JavaScript 인터프리터는 클로저를 다른 함수를 처리하는 방식과 다르게 취급하지 않습니다.
  2. 모든 기능에는 스코프 체인 이 포함됩니다. 클로저에는 별도의 참조 환경 이 없습니다 .
  3. 클로저는 다른 모든 기능과 같습니다. 우리는 그것들이 그들이 속한 범위 밖의 범위에서 참조 될 때 그것들을 클로저라고 부릅니다 . 이것은 흥미로운 경우 이기 때문 입니다.

40
세부 사항을 자세히 살펴보고 무슨 일이 일어나고 있는지 아주 잘 설명했기 때문에 받아 들였습니다. 그리고 마지막으로 클로저가 무엇인지 더 잘 이해했거나 JS에서 변수 바인딩이 어떻게 작동하는지 더 잘 알았습니다.
leemes

3
사례 1에서는 g의 범위에서 실행 setTimeout되지만 사례 2 f에서는 전역 범위에서 실행 한다고 말합니다 . 둘 다 setTimeout 내에 있으므로 차이점은 무엇입니까?
rosscj2533

9
이에 대한 출처를 말씀해 주시겠습니까? 한 범위에서 호출되었지만 다른 범위에서는 호출되지 않은 경우 함수가 클로저가 될 수있는 정의를 본 적이 없습니다. 따라서이 정의는 내가 익숙한보다 일반적인 정의의 하위 집합처럼 보입니다 (참조 는 클로저가 클로저 인 곳에서 호출되는 범위 또는 호출되지 않은 경우에도 kev의 답변 )!
Briguy37

11
@AaditMShah 클로저가 무엇인지에 대해 동의하지만 JavaScript의 일반 함수와 클로저 사이에 차이가있는 것처럼 말합니다 . 다른 점이 없다; 내부적으로 모든 함수에는 함수가 작성된 특정 범위 체인에 대한 참조가 포함됩니다. JS 엔진은 다른 경우로 간주하지 않습니다. 복잡한 점검 목록이 필요하지 않습니다. 모든 함수 객체가 어휘 범위를 가지고 있음을 알고 있습니다. 변수 / 속성이 전 세계적으로 사용 가능하다는 사실은 함수를 클로저로 만들지 않습니다 (단지 쓸모없는 경우입니다).
Peter

13
@ 피터-당신은 무엇을 알고, 당신은 정확합니다. 일반 함수와 클로저에는 차이가 없습니다. 나는 이것을 증명하기 위해 테스트를 실행했으며 당신의 호의를 얻었습니다 : 여기에 통제가 있고 여기에 대안이 있습니다. 당신이 말하는 것은 말이됩니다. JavaScript 인터프리터는 클로저를 위해 특별한 부기 작업을 수행해야합니다. 그것들은 단순히 일류 함수를 가진 어휘 범위 언어의 부산물입니다. 내 지식은 내가 읽은 내용 (제한된 내용)으로 제한되었습니다. 수정 해 주셔서 감사합니다. 동일한 내용을 반영하여 답변을 업데이트하겠습니다.
Aadit M Shah

96

closure정의 에 따르면 :

"클로저"는 변수 를 묶는 환경 (표현을 "닫는") 과 함께 자유 변수를 가질 있는 표현식 (일반적으로 함수)입니다 .

당신이 사용하는 closure당신은 함수의 외부에서 정의 된 변수를 사용하는 함수를 정의합니다. (우리는 변수를 자유 변수 라고 부릅니다 ).
그들은 모두 사용합니다 closure(첫 번째 예에서도).


1
세 번째 버전은 함수 외부에서 정의 된 변수를 어떻게 사용합니까?
Jon

1
@Jon은 반환 된 함수 사용 i2을 외부에서 정의합니다.
kev

1
@kev 함수 외부에서 정의 된 변수를 사용하는 함수를 정의한 경우 클로저를 사용하고 있습니다. ... 그런 다음 "Aadit M Shah"의 "사례 1 : 친구 프로그램"에서 "함수 f"입니다. 폐쇄? i (함수 외부에 정의 된 변수)를 사용합니다. 글로벌 범위가 결정자를 참조합니까?
internals-in


54

간단히 말해서 자바 스크립트 클로저 로 기능 할 수 액세스를 가변 되는 어휘 - 부모 함수에서 선언을 .

더 자세한 설명을 보자. 클로저를 이해하려면 JavaScript의 변수 범위를 이해하는 것이 중요합니다.

범위

JavaScript 범위는 함수로 정의됩니다. 모든 함수는 새로운 범위를 정의합니다.

다음 예제를 고려하십시오.

function f()
{//begin of scope f
  var foo='hello'; //foo is declared in scope f
  for(var i=0;i<2;i++){//i is declared in scope f
     //the for loop is not a function, therefore we are still in scope f
     var bar = 'Am I accessible?';//bar is declared in scope f
     console.log(foo);
  }
  console.log(i);
  console.log(bar);
}//end of scope f

f 인쇄를 호출

hello
hello
2
Am I Accessible?

이제 g다른 함수 내에 정의 된 함수가있는 경우를 고려해 봅시다 f.

function f()
{//begin of scope f
  function g()
  {//being of scope g
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

f어휘 부모 를 호출 합니다 g. 앞에서 설명했듯이 이제 우리는 두 가지 범위를 갖습니다. 범위 f와 범위g .

그러나 한 범위는 다른 범위 내에 "내"있으며, 하위 함수의 범위는 부모 함수 범위의 일부입니까? 부모 함수의 범위에서 선언 된 변수는 어떻게됩니까? 하위 기능의 범위에서 액세스 할 수 있습니까? 바로 폐쇄가 시작되는 곳입니다.

폐쇄

JavaScript에서 함수 g는 범위에 선언 된 변수 g뿐만 아니라 상위 함수 범위에 선언 된 변수에도 액세스 할 수 있습니다.f .

다음을 고려하십시오.

function f()//lexical parent function
{//begin of scope f
  var foo='hello'; //foo declared in scope f
  function g()
  {//being of scope g
    var bar='bla'; //bar declared in scope g
    console.log(foo);
  }//end of scope g
  g();
  console.log(bar);
}//end of scope f

f 인쇄를 호출

hello
undefined

라인을 보자 console.log(foo);. 이 시점에서 우리는 범위 내에 있으며 scope 에 선언 된 g변수에 액세스하려고합니다 . 그러나 앞에서 언급했듯이 여기서는 어휘 부모 함수에 선언 된 변수에 액세스 할 수 있습니다. 의 어휘 부모입니다 . 따라서 인쇄됩니다. 이제 라인을 보자 . 이 시점에서 우리는 범위 내에 있으며 scope 에 선언 된 변수에 액세스하려고합니다 . 현재 범위에서 선언되지 않았으며 함수 가의 부모가 아니므 로 정의되지 않았습니다.foofgfhello
console.log(bar);fbargbargfbar

실제로 우리는 어휘 "grand parent"함수의 범위에서 선언 된 변수에 액세스 할 수도 있습니다. 따라서 함수 h내에 정의 된 함수가 있다면g

function f()
{//begin of scope f
  function g()
  {//being of scope g
    function h()
    {//being of scope h
      /*...*/
    }//end of scope h
    /*...*/
  }//end of scope g
  /*...*/
}//end of scope f

다음 h함수의 범위에 선언 된 모든 변수에 액세스 할 수있을 것 h, g등을 f. 이것은 클로저 로 이루어집니다 . JavaScript 클로저 에서는 어휘 부모 함수, 어휘 그랜드 부모 함수, 어휘 그랜드 부모 함수 등에서 선언 된 모든 변수에 액세스 할 수 있습니다. 이는 범위 체인 으로 볼 수 있습니다 . scope of current function -> scope of lexical parent function -> scope of lexical grand parent function -> ... 어휘 부모가없는 마지막 부모 함수까지

윈도우 객체

실제로 체인은 마지막 부모 함수에서 멈추지 않습니다. 특별한 범위가 하나 더 있습니다. 전역 범위 . 함수에서 선언되지 않은 모든 변수는 전역 범위에서 선언 된 것으로 간주됩니다. 글로벌 스코프에는 두 가지 특성이 있습니다.

  • 전역 범위에서 선언 된 모든 변수는 모든 곳에서 액세스 가능
  • 전역 범위에서 선언 된 변수는 window객체 의 속성에 해당 합니다.

따라서 foo전역 범위에서 변수를 선언하는 두 가지 방법이 있습니다 . 함수에서 선언하지 않거나 foo윈도우 객체 의 속성 을 설정하여 .

두 시도 모두 클로저를 사용합니다.

더 자세한 설명을 읽었으므로 이제 두 솔루션 모두 클로저를 사용하는 것이 분명 할 수 있습니다. 그러나 확실하게 증거를 만들어 봅시다.

새로운 프로그래밍 언어를 만들어 보자. JavaScript-No-Closure. 이름에서 알 수 있듯이 JavaScript-No-Closure는 클로저를 지원하지 않는다는 점을 제외하면 JavaScript와 동일합니다.

다시 말해;

var foo = 'hello';
function f(){console.log(foo)};
f();
//JavaScript-No-Closure prints undefined
//JavaSript prints hello

자, JavaScript-No-Closure를 사용하는 첫 번째 솔루션에서 어떤 일이 발생하는지 봅시다.

for(var i = 0; i < 10; i++) {
  (function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2); //i2 is undefined in JavaScript-No-Closure 
    }, 1000)
  })();
}

따라서 이것은 인쇄됩니다 undefined JavaScript-No-Closure에서 10 번 .

따라서 첫 번째 솔루션은 클로저를 사용합니다.

두 번째 해결책을 보자.

for(var i = 0; i < 10; i++) {
  setTimeout((function(i2){
    return function() {
        console.log(i2); //i2 is undefined in JavaScript-No-Closure
    }
  })(i), 1000);
}

따라서 이것은 인쇄됩니다 undefined JavaScript-No-Closure에서 10 번 .

두 솔루션 모두 클로저를 사용합니다.

편집 :이 3 개의 코드 스 니펫이 전역 범위에 정의되어 있지 않은 것으로 가정합니다. 그렇지 않으면 변수 foo와 객체가 객체에 i바인딩 window되므로 windowJavaScript와 JavaScript-No-Closure 의 객체를 통해 액세스 할 수 있습니다 .


i정의되지 않은 이유는 무엇 입니까? 부모 범위 만 참조하면 클로저가없는 경우에도 여전히 유효합니다.
leemes

JavaScript-No-Closure에서 foo가 정의되지 않은 것과 같은 이유로. 어휘 상위에 정의 된 변수에 액세스 할 수있는 JavaScript 기능으로 인해 <code> i </ code>는 JavaScript에서 정의되지 않습니다. 이 기능을 폐쇄라고합니다.
brillout

이미 정의 된 변수와 자유 변수를 참조하는 것의 차이점을 이해하지 못했습니다 . 클로저에서는 외부 컨텍스트에서 바인딩해야하는 자유 변수를 정의합니다. 코드에서 함수를 정의 할 때 방금 설정 i2 했습니다 i. 이것은 i자유 변수 가 아닙니다. 여전히, 우리는 함수를 클로저로 간주하지만 자유 변수가 없으면 이것이 핵심입니다.
leemes

2
@leemes, 동의합니다. 그리고 받아 들인 대답과 비교할 때 실제로 실제로 진행되고있는 것을 보여주지는 않습니다. :)
Abel

3
나는 이것이 클로저를 일반적으로 간단하게 설명하고 특정 유스 케이스에 들어간 가장 좋은 대답이라고 생각합니다. 감사!
팀 피터슨

22

나는 누군가가 이것을 설명하는 방식에 결코 행복하지 않았습니다.

클로저를 이해하는 열쇠는 클로저가없는 JS의 모습을 이해하는 것입니다.

클로저가 없으면 오류가 발생합니다.

function outerFunc(){
    var outerVar = 'an outerFunc var';
    return function(){
        alert(outerVar);
    }
}

outerFunc()(); //returns inner function and fires it

outerFunc가 가상의 클로저 비활성화 버전의 JavaScript로 반환되면 outerVar에 대한 참조는 가비지 수집되어 내부 함수가 참조 할 수있는 부분이 없어집니다.

클로저는 본질적으로 내부 함수가 외부 함수의 변수를 참조 할 때 해당 변수가 존재할 수 있도록하는 특수 규칙입니다. 클로저를 사용하면 외부 함수가 수행되거나 포인트를 기억하는 데 도움이되는 경우 '닫힌'후에도 참조 된 var가 유지됩니다.

클로저를 사용하더라도 로컬을 참조하는 내부 펑크가없는 함수에서 로컬 var의 수명주기는 클로저리스 버전에서와 동일하게 작동합니다. 함수가 완료되면 지역 주민은 가비지 수집을받습니다.

내부 펑크에서 외부 var에 대한 참조가 있으면 도어 잼이 참조 var에 대한 가비지 수집 방식과 유사합니다.

클로저를 보는 더 정확한 방법은 내부 함수가 기본적으로 내부 범위를 자체 범위 소리로 사용한다는 것입니다.

그러나 참조 된 컨텍스트는 사실 스냅 샷과는 달리 영구적입니다. 외부 함수의 로컬 변수를 계속 증가시키고 로깅하는 반환 된 내부 함수를 반복적으로 실행하면 더 높은 값을 경고합니다.

function outerFunc(){
    var incrementMe = 0;
    return function(){ incrementMe++; console.log(incrementMe); }
}
var inc = outerFunc();
inc(); //logs 1
inc(); //logs 2

당신은 '스냅 샷'에 대해 옳습니다 (제 생각에 당신은 내 대답을 말합니다). 나는 행동을 나타내는 단어를 찾고 있었다. 귀하의 예에서는 '핫 링크'폐쇄 구조로 볼 수 있습니다. 내부 함수에서 클로저를 매개 변수로 잡을 때 '스냅 샷'으로 동작한다고 말할 수 있습니다. 그러나 나는 오용 된 단어가 주제에 혼란을 더할 뿐이라는 데 동의합니다. 그것에 대한 제안이 있으면 답변을 업데이트하겠습니다.
Andries

내부 함수에 명명 된 함수를 제공하면 설명에 도움이 될 수 있습니다.
Phillip Senn

클로저가 없으면 존재하지 않는 변수를 사용하려고하므로 오류가 발생합니다.
Juan Mendes

흠 ... 좋은 지적. 정의되지 않은 var를 참조하면 궁극적으로 전역 객체의 속성으로 조회되거나 정의되지 않은 var에 대한 할당과 혼동되기 때문에 오류가 발생하지 않았습니까?
Erik Reppen

17

둘 다 클로저를 사용하고 있습니다.

나는 여기 Wikipedia 정의 와 함께 갈 것이다.

컴퓨터 과학에서 클로저 (어휘 폐쇄 또는 함수 클로저)는 참조 환경과 함께 함수에 대한 함수 또는 참조입니다. 해당 함수의 로컬 변수가 아닌 각 변수 (자유 변수라고도 함)에 대한 참조를 저장하는 테이블 . 일반 함수 포인터와 달리 클로저는 함수가 즉각적인 어휘 범위를 벗어난 경우에도 로컬이 아닌 변수에 액세스 할 수 있도록합니다.

친구의 시도 i는 값을 가져 와서 로컬에 저장할 사본을 만들어 로컬이 아닌 변수를 명확하게 사용합니다 i2.

자신의 시도는 i(콜 사이트의 범위에있는) 익명 함수에 인수로 전달합니다. 이것은 지금까지의 클로저가 아니지만 해당 함수는 동일한를 참조하는 다른 함수를 반환합니다 i2. 내부 익명 함수 내부 i2는 로컬이 아니므로 클로저가 생성됩니다.


네,하지만 요점은 그가 어떻게 하고 있는지 생각 합니다. 다만 사본 그는 i하려면 i2, 다음 몇 가지 논리를 정의하고이 기능을 실행합니다. 내가 즉시 실행 하지 않고 var에 저장하고 루프 후에 실행하면 10을 인쇄하지 않습니까? 그래서 i를 포착 하지 못했습니다 .
leemes

6
@leemes : 잘 캡처되었습니다 i. 설명하는 동작은 폐쇄 대 비 폐쇄의 결과가 아닙니다. 그 동안 클로즈 오버 변수가 변경 된 결과입니다. 함수를 즉시 호출하고 i인수 (현재 값을 그 자리에 복사 하는) 로 전달하여 다른 구문을 사용하여 동일한 작업을 수행합니다 . setTimeout다른 setTimeout것을 안에 넣으면 같은 일이 일어날 것입니다.
Jon

13

당신과 당신의 친구는 모두 폐쇄를 사용합니다 :

클로저는 함수와 해당 함수가 작성된 환경의 두 가지를 결합한 특별한 종류의 객체입니다. 환경은 클로저가 생성 될 때 범위 내에 있던 로컬 변수로 구성됩니다.

MDN : https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Closures

function(){ console.log(i2); }익명 함수의 폐쇄 내부에 정의 된 친구의 코드 함수에서 function(){ var i2 = i; ...로컬 변수를 읽고 쓸 수 있습니다 i2.

코드 함수에서 함수 function(){ console.log(i2); }폐쇄 내부에 정의되어 function(i2){ return ...있으며 로컬 가치를 읽거나 쓸 수 있습니다 i2(이 경우 매개 변수로 선언).

두 경우 모두 함수는 function(){ console.log(i2); }로 전달됩니다 setTimeout.

메모리 사용량이 적은 다른 방법은 다음과 같습니다.

function fGenerator(i2){
    return function(){
        console.log(i2);
    }
}
for(var i = 0; i < 10; i++) {
    setTimeout(fGenerator(i), 1000);
}

1
귀하의 솔루션과 내 친구의 솔루션이 왜 "더 빠르며 메모리 사용률이 더 낮은"이유를 알 수 없습니까?
brillout

3
솔루션에서 20 개의 함수 객체 (각 루프에 2 개의 객체 : 2x10 = 20)를 만듭니다. 프렌드 솔루션에서도 동일한 결과가 나타납니다. "my"솔루션에서는 11 개의 함수 객체 만 작성됩니다. 1 for for loop 및 10 "inside"-1 + 1x10 = 11. 결과적으로 메모리 사용량이 줄어들고 속도가 향상됩니다.
앤드류 D.

1
이론적으로는 사실이다. 실제로, 또한 :이 JSPerf 벤치 마크를 참조하십시오 : jsperf.com/closure-vs-name-function-in-a-loop/2
Rob W

10

폐쇄

클로저는 함수가 아니며 표현식이 아닙니다. 함수 스코프 외부에서 사용 된 변수와 함수 내부에서 사용되는 일종의 '스냅 샷'으로 표시되어야합니다. 문법적으로, '변수를 닫습니다'라고 말해야합니다.

다시 말하면, 클로저는 함수가 의존하는 관련 변수 컨텍스트의 복사본입니다.

한 번 더 (naïf) : 클로저가 매개 변수로 전달되지 않은 변수에 액세스 할 수 있습니다.

이 기능 개념은 사용하는 프로그래밍 언어 / 환경에 따라 크게 달라집니다. JavaScript에서 클로저는 어휘 범위 (대부분의 c 언어에서 적용됨)에 의존합니다.

따라서 함수를 반환하는 것은 대부분 익명 / 명명되지 않은 함수를 반환합니다. 함수가 변수로 액세스하지 않고 매개 변수로 전달되지 않고 (어휘) 범위 내에서 클로저가 발생했습니다.

따라서 귀하의 예와 관련하여 :

// 1
for(var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i); // closure, only when loop finishes within 1000 ms,
    }, 1000);           // i = 10 for all functions
}
// 2
for(var i = 0; i < 10; i++) {
    (function(){
        var i2 = i; // closure of i (lexical scope: for-loop)
        setTimeout(function(){
            console.log(i2); // closure of i2 (lexical scope:outer function)
        }, 1000)
    })();
}
// 3
for(var i = 0; i < 10; i++) {
    setTimeout((function(i2){
        return function() {
            console.log(i2); // closure of i2 (outer scope)

        }
    })(i), 1000); // param access i (no closure)
}

모두 폐쇄를 사용하고 있습니다. 실행 지점과 클로저를 혼동하지 마십시오. 클로저의 '스냅 샷'을 잘못된 순간에 가져 오면 값이 예상치 못한 것일 수 있지만 확실히 클로저가 발생합니다!



10

두 가지 방법을 살펴 보자.

(function(){
    var i2 = i;
    setTimeout(function(){
        console.log(i2);
    }, 1000)
})();

setTimeout()자체 컨텍스트 내에서 실행되는 익명 함수를 선언하고 즉시 실행합니다 . 의 현재 값은 i복사본을 i2먼저 만들어서 유지됩니다 . 즉각적인 실행으로 인해 작동합니다.

setTimeout((function(i2){
    return function() {
        console.log(i2);
    }
})(i), 1000);

의 현재 값 i이 보존되는 내부 함수에 대한 실행 컨텍스트를 선언합니다 i2. 이 방법은 즉시 실행을 사용하여 값을 유지합니다.

중대한

실행 시맨틱은 두 가지 접근법 사이에서 동일하지 않다는 것을 언급해야한다. 당신의 내부 함수는 setTimeout()그의 내부 함수 호출에 전달되는 반면setTimeout() 자체를 합니다.

두 코드를 다른 코드 안에 래핑 setTimeout()한다고해서 두 번째 접근 방식 만이 클로저를 사용한다는 것을 증명하지는 않습니다. 처음에는 똑같은 것이 없습니다.

결론

두 방법 모두 클로저를 사용하므로 개인 취향에 따라 다릅니다. 두 번째 방법은 이동하거나 일반화하기가 더 쉽습니다.


그 차이는 다음과 같습니다. 그의 솔루션 (1st)은 참조로 캡처하고 내 (2nd)는 값으로 캡처합니다. 이 경우에는 차이가 없지만 다른 setTimeout에 실행을 넣으면 그의 솔루션에 문제가 있지만 현재는 아니라 i의 최종 값을 사용하지만 내 실은 사용합니다 현재 값 (값으로 캡처 한 이후)
leemes

@leemes 둘 다 같은 방식으로 캡처합니다. 함수 인수 또는 할당을 통해 변수를 전달하는 것도 마찬가지입니다 ... 실행을 다른 것으로 래핑하는 방법에 대한 질문에 추가 할 수 setTimeout()있습니까?
Ja͢ck

이것을 확인해 보자 ... 함수 객체가 전달 i될 수 있고 실행 위치 또는 시간에 따라 인쇄 기능에 영향을 미치지 않고 원래 변수 를 변경할 수 있음 을 보여주고 싶었 습니다.
leemes

잠깐, (외부) setTimeout에 함수를 전달하지 않았습니다. 그것들을 제거 ()하여 함수를 전달하면 출력의 10 배가 보입니다 10.
leemes

@leemes 앞에서 언급했듯이, ()코드는 여러분의 코드처럼 작동합니다 (i). 당신은 단지 그의 코드를 감싸지 않고 그것을 변경했습니다. 따라서 더 이상 유효한 비교를 할 수 없습니다.
Ja͢ck

8

클로저가 무엇인지, JS에서 어떻게 작동하는지 상기시키기 위해 얼마 전에 이것을 썼습니다.

클로저는 호출 될 때 호출 된 범위가 아니라 선언 된 범위를 사용하는 함수입니다. 자바 스크립트에서 모든 함수는 다음과 같이 동작합니다. 범위의 변수 값은 여전히 ​​해당 함수를 가리키는 한 지속됩니다. 규칙에 대한 예외는 'this'입니다.이 함수는 함수가 호출 될 때 내부에있는 개체를 나타냅니다.

var z = 1;
function x(){
    var z = 2; 
    y(function(){
      alert(z);
    });
}
function y(f){
    var z = 3;
    f();
}
x(); //alerts '2' 

6

면밀히 조사한 후, 둘 다 폐쇄를 사용하고있는 것 같습니다.

친구의 경우 i익명 함수 1 내 i2에서 액세스하고이있는 익명 함수 2에서 액세스 console.log합니다.

귀하의 경우 존재하는 i2익명 함수 내부 에 액세스하고 있습니다 console.log. 크롬 개발자 도구에서 "Scope variables"아래에 debugger;문장을 추가하면 console.log변수의 범위를 알려줍니다.


2
더 구체적인 이름이 없으므로 오른쪽 패널의 "Closure"섹션이 사용됩니다. "로컬"은 "클로저"보다 더 강력한 표시입니다.
Rob W


4

다음을 고려하세요. 이것은에 f닫히지 i만 다른 함수 를 닫는 함수 를 만들고 다시 만듭니다 ! :

i=100;

f=function(i){return function(){return ++i}}(0);
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

f=function(i){return new Function('return ++i')}(0);        /*  function declarations ~= expressions! */
alert([f,f(),f(),f(),f(),f(),f(),f(),f(),f(),f()].join('\n\n'));

다음은 "a"함수 "자체"
(자체!이 후 스 니펫은 단일 지시자를 사용합니다 f)

for(var i = 0; i < 10; i++) {
    setTimeout( new Function('console.log('+i+')'),  1000 );
}

또는 더 명확하게 :

for(var i = 0; i < 10; i++) {
    console.log(    f = new Function( 'console.log('+i+')' )    );
    setTimeout( f,  1000 );
}

NB. 마지막 정의 f되어 function(){ console.log(9) } 전에 0 인쇄됩니다.

경고! 클로저 개념은 기본 프로그래밍의 본질에서 강렬한 방해가 될 수 있습니다.

for(var i = 0; i < 10; i++) {     setTimeout( 'console.log('+i+')',  1000 );      }

x-refs .:
JavaScript 클로저는 어떻게 작동합니까?
자바 스크립트 클로저 설명
(JS) 클로저에는 함수 내부에 함수가 필요
합니까? 자바 스크립트에서 클로저를 이해하는 방법은 무엇입니까?
자바 스크립트 로컬 및 글로벌 변수 혼란


스 니펫이 처음 시도-제어 방법을 Run' only was desired - not sure how to remove the 모르는 경우
복사

-1

저의 예와 폐쇄에 대한 설명을 나누고 싶습니다. 파이썬 예제와 스택 상태를 보여주기 위해 두 개의 그림을 만들었습니다.

def maker(a, b, n):
    margin_top = 2
    padding = 4
    def message(msg):
        print('\n * margin_top, a * n, 
            ' ‘ * padding, msg, '  * padding, b * n)
    return message

f = maker('*', '#', 5)
g = maker('', '♥’, 3)

f('hello')
g(‘good bye!')

이 코드의 출력은 다음과 같습니다.

*****      hello      #####

      good bye!    ♥♥♥

다음은 스택과 함수 객체에 연결된 클로저를 보여주는 두 개의 그림입니다.

메이커에서 함수를 돌려주는 경우

함수가 나중에 호출 될 때

함수가 매개 변수 또는 비 로컬 변수를 통해 호출되면 코드에는 margin_top, 패딩 및 a, b, n과 같은 로컬 변수 바인딩이 필요합니다. 함수 코드가 작동하도록하려면 오래 전에 사라진 메이커 함수의 스택 프레임에 액세스 할 수 있어야하며, 함수 메시지 객체와 함께 찾을 수있는 클로저에 백업됩니다.


이 답변을 삭제하고 싶습니다. 나는 질문이 폐쇄에 관한 것이 아니라는 것을 깨달았으므로 다른 질문으로 옮기고 싶습니다.
이은정

2
본인의 콘텐츠를 삭제할 수 있다고 생각합니다. delete답변 아래 의 링크를 클릭하십시오 .
Rory McCrossan
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.