클로저란?

클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.

클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.

 

 

다음예제를 확인해보자

   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변수를 가지고 있었다면, name대신 this.name을 사용했을 것이다.

만약 const를 썼더라면 this를 무조건 써야한다.

 

하지만 실행해 보면 잘 출력되는것을 확인 할 수 있다.

 

let과 const를 사용한 범위 지정

ES6 이전 전통적인 JavaScript에는 함수 스코프전역 스코프 두 가지만 존재했다. 

 

var로 선언한 변수는 함수 내부 또는 외부에서 선언되었는지에 따라 함수 스코프 또는 전역 스코프를 가진다.

 

이때, 중괄호로 표시된 블록이 스코프를 생성하지 않는다는 점에서 혼란을 일으킬 수 있다.

 

아래 예시를 확인해보자

   if (Math.random() > 0.5) {
      var x = 1;
    } else {
      var x = 2;
    }
    console.log(x);

 

C나 Java와 같이 블록이 스코프를 생성하는 언어의 경우,

위 코드의 console.log 라인에서 x가 어떤 블록 스코프에도 포함되지 않기 때문에 에러가 발생해야 할 것이다. 

 

그러나 블록은 var로 선언한 변수에 대해 스코프를 생성하지 않기 때문에 여기서 var 명령문은 전역 변수를 생성한다.

이것을 클로저와 함께 사용했을 때 어떤 버그가 발생할 수 있는지 실제 예제가 아래 소개되어 있다.

 

ES6에서 JavaScript는 블록 스코프 변수를 생성할 수 있도록 let const 선언과 함께 시간상 사각지대 등을 도입했다.

  if (Math.random() > 0.5) {
      const x = 1;
    } else {
      const x = 2;
    }
    console.log(x); // ReferenceError: x is not defined

요약하자면, ES6부터 블록은 스코프로 취급되기 시작했지만,

이는 let const로 변수를 선언했을 때만 유효하다.

또한, ES6에서 모듈을 도입하면서 또 다른 스코프를 제공하게 되었다.

추후 소개하겠지만, 클로저는 이 모든 스코프의 변수를 캡처할 수 있다.

 

 

이제 다음 예제를 보자:

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

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

 

이 코드는 바로 전의 예제와 완전히 동일한 결과가 실행된다.

 

하지만 흥미로운 차이는 displayName()함수가 실행되기 전에 외부함수인 makeFunc()로부터 리턴되어

 myFunc 변수에 저장된다는 것이다.

 

한 눈에 봐서는 이 코드가 여전히 작동하는 것이 직관적으로 보이지 않을 수 있다.

몇몇 프로그래밍 언어에서, 함수 안의 지역 변수들은 그 함수가 처리되는 동안에만 존재한다.

 makeFunc() 실행이 끝나면(displayName함수가 리턴되고 나면) name 변수에 더 이상 접근할 수 없게 될 것으로 예상하는 것이 일반적이다.

 

하지만 위의 예시와 자바스크립트의 경우는 다르다. 그 이유는 자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하기 때문이다. 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다.

이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.(var로 변수를 선언했기 때문이다.)

 

첫 번째 예시의 경우, myFunc은 makeFunc이 실행 될 때 생성된 displayName 함수의 인스턴스에 대한 참조다. 

displayName의 인스턴스는 변수 name 이 있는 어휘적 환경에 대한 참조를 유지한다.

이런 이유로 myFunc가 호출될 때 변수 name은 사용할 수 있는 상태로 남게 되고 "Mozilla" 가 alert 에 전달된다.

 

 

다음은 조금 더 흥미로운 예제인 makeAdder 함수이다

 

    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값에 접근하여 값을 계산

add5와 add10은 둘 다 클로저이다.

이들은 같은 함수 본문 정의를 공유하지만 서로 다른 맥락(어휘)적 환경을 저장한다.

함수 실행 시 add5의 맥락적 환경에서 클로저 내부의 x는 5 이지만 add10의 맥락적 환경에서 x는 10이다.

또한 리턴되는 함수에서 초기값이 1로 할당된 y에 접근하여 y값을 100으로 변경한 것을 볼 수 있다.

(물론 x값도 동일하게 변경 가능하다.)

이는 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주는 포인트이며

클로저에 단순히 값 형태로 전달되는것이 아니라는 것을 의미한다.

 

참고사이트 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures#let%EA%B3%BC_const%EB%A5%BC_%EC%82%AC%EC%9A%A9%ED%95%9C_%EB%B2%94%EC%9C%84_%EC%A7%80%EC%A0%95

 

 

js의 접근제어자라고 생각하면 된다.

지역변수이지만 전역변수처럼 사용한다.(변수의 보호가 목적)

 

 

클로저 를 사용하여 전역 변수를 로컬로 만들 수 있습니다 .

함수가 개인변수를 가질 수 있게 된다.

 

클로저 예시

 

 

 

 

 

클로저 연구해보기

const add를 const add = (); 형식으로 선언한뒤

console.log(add); 하면 return 된 function {} 진행문(함수 표현식)이 나온다.

여기까지 하면 카운터변수가 0 할당이 되고 결국 add는 함수가 된다.

let counter는 블록 스코프 영향을 받는다.

큰 function 스코프안에 블록스코프가 있으므로 안에 있는 블록스코프도 똑같은 counter 변수로 받는다.

(변수의 유효범위가 중요한 이유다.)

add(); 함수를 호출 될때마다 1씩 증가한다.

이를 JavaScript 클로저라고 한다.

 

자바스크립트에는 class의 개념이 약하다

하지만 클로저나 this키워드를 이용하여

다른언어들과 같이 class 기능같이 쓸 수 있다.

 

 

하지만 단점이 있다.

위예제는 가비지 컬렉터가 삭제할 수 없다.

그래서 브라우저를 계속 지속시 할당 메모리가 커질 수 밖에 없다

 

 

참고사이트 :  https://www.w3schools.com/js/js_function_closures.asp

 

'Web > javascript' 카테고리의 다른 글

arguments 객체  (0) 2023.01.16
쿠키  (0) 2023.01.13
JS Type 형 변환 테이블  (0) 2023.01.10
JS if문 , switch문 , 반복문  (0) 2023.01.10
js 디버깅 브라우저(크롬)에서  (0) 2023.01.10

+ Recent posts