As i wish

[JavaScript] Closure 본문

JavaScript

[JavaScript] Closure

어면태 2019. 7. 8. 17:35
클로저란?
클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.

무슨말인지 모르겠다....

MDN web docs를 보면 조금 이해가 편하다.

 

클로저

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하기 위해서는 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

developer.mozilla.org

function init() {
  var name = "Mozilla"; // name은 init 에 의해 생성된 지역변수다
  function displayName() { // displayName() 은 내부 함수이며,클로저다
    alert(name); // 부모 함수에서 선언된 변수를 사용한다
  }
  displayName();
}
init();
init()은 지역 변수 name과 함수 displayName()을 생성한다. displayName() init()안에 정의된 내부 함수이며 init() 함수 본문에서만 사용할 수 있다. displayName()는 자신의 지역 변수가 없다. 하지만 내부 함수는 외부 함수에 접근할 권한을 가지고 있기 때문에 displayName()는 부모 함수 init()에서 선언된 변수 name에 접근할 수 있다. 만약 displayName()내에서 동일한 이름의 name 변수가 이미 있으면 내부의 변수가 우선적으로 사용된다.

코드를 실행하면 displayName() 함수 안의 alert() 명령어가 부모 함수에서 선언된 name 변수의 값을 성공적으로 출력한다. 이것은 함수가 중첩됐을 때 파서가 어떻게 변수를 처리하는지 보여주는 어휘적유효 범위의 한 예이다."Lexical"은 유효 범위를 지정할 때 변수가 사용가능한 범위를 결정하기 위해 소스 코드 내에서 변수가 선언된 위치를 사용한다는 것을 의미한다. 결론적으로 중첩된 함수의 내부 함수들은 그들의 외부 유효 범위 내에서 선언된 변수들에 접근할 권한을 가진다.
function makeFunc() {
  var name = "Mozilla";
  function displayName() {
    alert(name);
  }
  return displayName;
}

var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
이 코드는 바로 전의 예제와 완전히 동일한 결과가 실행된다. 하지만 흥미로운 차이는displayName()함수가 실행되기 전에 외부함수인 makeFunc()로부터 리턴되어 myFunc 변수에 저장된다는 것이다. 한 눈에 봐서는 이 코드가 여전히 작동하는 것이 직관적으로 보이지 않을 수 있다. 몇몇 프로그래밍 언어에서, 함수 안의 지역 변수들은 그 함수가 처리되는 동안에만 존재한다. makeFunc() 실행이 끝나면(displayName함수가 리턴되고 나면) name 변수에 더 이상 접근할 수 없게 될 것으로 예상하는 것이 일반적이다.

하지만 위의 예시와 자바스크립트의 경우는 다르다. 그 이유는 자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하기 때문이다. 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다. 첫 번째 예시의 경우, myFunc은 makeFunc이 실행될 때 생성된 displayName 함수의 인스턴스에 대한 참조다. displayName의 인스턴스는 변수 name 이 있는 어휘적 환경에 대한 참조를 유지한다. 이런 이유로 myFunc가 호출될 때 변수 name은 사용할 수 있는 상태로 남게 되고 "Mozilla" 가 alert 에 전달된다.

 

조금 더 쉽게 설명하면 클로저 = 함수 + 함수가 선언된 환경 이라고 보면 좋을듯 합니다. 함수가 선언된 환경 이라 함은 클로저가 생성 될 때 그 범위 안에 있던 여러 지역 변수들로 이루어집니다.

        function foo() {
          var a = "text";
          function bar() {
            console.log(a);
          }
          return bar;
        }

        var closure = foo();
        closure(); // text

위에서 언급했던 것 처럼 foo 함수의 리턴 값은 내부 함수인 bar입니다. foo 함수가 종료되면 안에 있던 지역 변수는 사라지는게 상식이지만 정상 출력되는 것을 볼 수 있는데 이는 바로 closure 함수가 클로저 이기 때문이죠. closure는 bar함수와 bar함수가 선언된 환경 즉, "text" 문자열을 포함한 클로저가 되겠죠.

 

반복문 안에서 클로저

자바스크립트로 코드를 작성하다 보면 흔히들 하는 실수가 있는데요. 이는 자바스크립트에 클로저 때문 입니다.

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

        foo();

예상 결과로는 1, 2, 3, 4... 9 가 나와야 하지만 예상과 다르게 9 가 열번 나오게 되죠. 이는 바로 클로저 때문입니다. foo 함수가 종료되고 console.log로 i 값을 출력하게 되는데, 하나의 환경(하나의 Lexical environement) 를 공유하기 때문에 i 가 종료 될 때의 값이 9 를 열번 출력하게 되는거죠.

 

이를 해결 하기 위해선 두가지 방법이 있는데, 하나의 환경을 공유하지 않게 하거나 블록 스코프를 사용하는 법입니다.

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

        foo();

위 처럼 새롭게 변수를 다시 받아서 setTimeout 호출 시 새로운 변수를 출력하게 하는 방법입니다.

        function foo() {
          for (let i=0; i<10; i++) {
            setTimeout(function() {
              console.log(i);
            }, 100);
          }
        }

        foo();

두 번째는 let를 사용하여 블록 스코프를 형성하는 방법이 되겠죠.

'JavaScript' 카테고리의 다른 글

[JavaScript] async, await  (0) 2019.11.14
[JavaScript] splice, slice, split  (0) 2019.08.13
[JavaScript] Scope와 Hoisting  (0) 2019.07.08
[You don't know JS] - 비동기성: 지금과 나중  (0) 2019.06.15
[You don't know JS] - 작동 위임  (0) 2019.06.07
Comments