[Javascript] 클로저 Closure

백광현·2022년 9월 20일
0

⭐️ 클로저

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

    function init() {
      var name = "Mozilla"; // name은 init에 의해 생성된 지역 변수이다.
      function displayName() { // displayName() 은 내부 함수이며, 클로저다.
        alert(name); // 부모 함수에서 선언된 변수를 사용한다.
      }
      displayName();
    }
    init();

init은 지역 변수 name과 함수 displayName을 생성. displayName 내부엔 지역 변수가 없다. 그런데 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 displayName이 부모 함수 init 안에서 선언된 변수 name에 접근을 할 수 있다. displayName이 자신만의 name 변수를 가지고 있었다면, **name 대신 this.name을 사용했을 것이다.**

이는 어휘적 범위 지정(lexial scoping)의 한 예이다. lexical은 어휘적 범위 지정(lexial scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스 코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다. 중첩된 함수는 외부 범위에서 선언한 변수에도 접근할 수 있다

let과 const를 사용한 범위 지정

ES6 이전 var만 사용 했던 Javascript에선 함수 스코프와 전역스코프 두가지만 존재했다.
var로 선언한 변수는 함수 내부에서 선언되었을때만 블록 스코프가 되며 if,for문 등 다른 중괄호로 표시된 블록 안에서는 스코프를 생성하지 않아 혼란을 일으킬 수 있는 여지가 있었다.

if (Math.random() > 0.5) {
      var x = 1;
    } else {
      var x = 2;
    }
    console.log(x); // 콘솔 잘 찍힘.

Javascript의 경우 var로 선언된 변수는 함수에서만 블록 스코프가 되어 조건문 안에 있는 x는 글로벌 스코프가 되기에 콘솔이 잘 찍힌다.

ES6에선 호이스팅 뿐만 아니라 이러한 문제도 해결하기 위해 let과 const를 도입하며 시간상 사각지대 등을 같이 도입하였다.

    if (Math.random() > 0.5) {
      const x = 1;
    } else {
      const x = 2;
    }
    console.log(x); // ReferenceError: x is not defined
					// const와 let은 조건문,반복문 등에서도 지역스코프가 된다.

비슷한 다음 예제

    function makeFunc() {
      var name = "Mozilla";
      function displayName() {
        alert(name);
      }
      return displayName;
    }

    var myFunc = makeFunc();
    //myFunc변수에 displayName을 리턴함
    //유효범위의 어휘적 환경을 유지
    myFunc();
    //리턴된 displayName 함수를 실행(name 변수에 접근)

이 코드는 바로 전 코드와 같을까? 동일한 결과가 실행된다. 하지만 차이가 있다면 displayName 함수가 실행되기 전에 외부함수인 makeFunc로 부터 리턴되어 myFunc 변수에 저장된다는 것이다.

솔직히 처음 봤을때는 무슨차이가 있나 싶었다. makeFunc 함수의 실행이 끝나면 name 변수에 더이상 접근할 수 없을 걸로 생각했었다.

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

이 다음 예제를 한번 보자

function makeAdder(x) {
      var y = 1;
      return function(z) {
        y = 100;
        return x + y + z;
      };
    }

    var add5 = makeAdder(5);
    var add10 = makeAdder(10);
    //클로저에 x와 y의 환경이 저장됨

    console.log(add5(2));  // 107 (x:5 + y:100 + z:2)
    console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
    //함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산

이 예제에서 단일 인자 x를 받아서 새 함수를 반환하는 함수 makeAdder(x)를 정의했다. 반환되는 함수는 단일인자 z를 받아서 x와 y와 z의 합을 변환하다.

본질적으로 makeAdder는 함수를 만들어내는 공장이다. 이는 makeAdder 함수가 특정한 값을 인자로 가질 수 있는 함수들을 리턴한다는 것을 의미. 위 코드에서 add5, add10 두 개의 새로운 함수들을 만들기 위해 makeAdder 함수를 사용. 하나는 매개변수 x에 5를 더하고 다른 하나는 매개변수 x에 10을 더한다.

add5add10은 둘 다 클로저이다. 이 둘은 같은 함수 본문 정의를 공유하지만 서로 어휘적 환경을 저장한다. 함수 실행시에 add5의 맥락적 환경에서 클로저 내부의 x는 5이지만 add10의 맥락적 환경에서 x는 10이다. 또한 리턴되는 함수에서 초기값이 1로 할당된 y에 접근하여 y 값을 100으로 변경한 것을 볼 수 있다. (물론 x값도 동일하게 변경 가능하다.) 이는 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주는 포인트이며 클로저에 단순히 값 형태로 전달되는것이 아니라는 것을 의미한다.

요약

  • 클로저란 함수와 함수가 선언된 어휘적 환경의 조합이다
  • 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.

솔직히 오늘 클로저 처음 본거 아니였다... 근데 첨에 봤을땐 이게 뭔지 싶었다. 여러 블로그를 돌아다녔지만 결국 이해시켜준건 mdn. 충성충성

0개의 댓글