본문 바로가기
Programming/WEB

[JS] javascript Closure(클로저) 정리

by 코딩의성지 2022. 2. 19.

javascript를 공부하시다 보면 처음으로 '아... javascript 쉽다고 들었는데... 어렵네... 하는 구간이 있다.'
바로 이 Closure를 마주치는 순간이 그 순간 중 하나가 아닐까 생각한다.

오늘은 이 Closure 에 대해 정리해두고자 하니, 잘 따라오시길 바란다.

Closure


"클로저는 함수와 그 함수가 선언 됐을 때의 렉시컬 환경과의 조합이다."
MDN 에 나온 클로저의 정의이다. 이걸보고 오 ~ 클로저 별거 아니네 하면 ... 당신은 천재다.

(대부분의 경우는 잘 모르실 것이라 예상을 하고 ..!) 클로저를 비교적 쉽게 설명드리도록 하겠다.
자바스크립트는 어휘적 환경( Lexical Environment)을 가진다.
이 말이 무슨말인지를 지금부터 잘 기억하길 바란다.

아래 예제를 보자.

이 프로그램이 실행되면 아래처럼 전역 어휘적 환경 ( Lexical Environment) 에 numOne 변수와 addNumOne 함수가 자리잡게 된다. 여기서 numOne 변수는 초기화가 되지 않아 사용이 불가한 상태일 것이고, addNumOne은 함수선언을 했기에 바로 사용 가능한 상태가 된다.


그리고 다음 라인인

let numOne;

을 읽으면..!

numOne 상태가 undefined로 바뀔 것이다.


그리고 1을 대입하면..

이렇게 전역 Lexical 환경에 값들이 놓이게 된다.

다음 마지막 라인의

addNumOne(5);

가 실행되게 되면, addNumOne 이라는 새로운 내부 어휘적 환경이 만들어지게 된다. 이러한 내부 어휘적 환경은
함수가 넘겨 받은 매개변수와 지역 변수가 저장된다.

그리고 각 Lexical 환경에서 위의 Lexical 환경을 참조할 수 있는데, 여기서는
addNumOne Lexical 환경 안에 num 값은 있는데, numOne 값이 없으니깐 그 값을 전역 Lexical환경에서 찾아 온다.

그래서 결과가 1+ 5 해서 6 이 나오게 되는 것이다.


자...! 클로저가 조금은 이해가 됐는가?

하나 더 예를 들어보겠다.

이런 예시가 있다. 위에서 한 것 처럼 하나하나 차례대로 뜯어보도록 하겠다.

먼저 프로그램이 실행되면 addFunc 와 addFunc2가 전역 어휘적 환경에 올라간다.
여기서 addFunc는 함수 선언이기 때문에 바로 사용이 가능하고, addFunc2는 초기화가 되지 않았기 때문에 사용이 불가능하다.


쭉 내려가서

const addFunc2 = addFunc(2);

이 행이 실행 될때, addFunc에 대한 Lexical 환경이 생성된다.
이 환경에는 전달 받은 매개변수 x의 값이 들어가게 된다. 그리고 addFunc2는 리턴하는 함수로 초기화 하게 된다.


그다음

 console.log(addFunc2(3));


줄을 읽으면 , 리턴 하는 함수가 실행되게 되는데, 이때 function(y){...} 이라는 익명함수에 대한 Lexical 환경이 생성된다.
여기에는 매개변수인 y값이 들어간다.

그림에서 보이는 것처럼
익명함수 렉시컬 환경 -> addFunc 렉시컬환경 -> 전역 렉시컬환경 순으로 참조하여 변수를 찾아갈 것이다.

이렇게 하면 결과는 ..! 당연히 5+3 = 8 이 잘 나오게 될 것이다.


이게 바로 클로저다..! 이해되는가?

Closure의 활용


클로저는 실무 프론트 단 코드에서 다방면으로 이용이 가능하다.
클로저는 자신이 생성될 때의 환경을 기억해야하기에 메모리 적인 측면에서는 손해를 보긴하지만, 그래도 그 강력한 기능은 무시하지 못한다.

클로저를 활용하는 두가지 예시를 들어보겠다.

상태유지

클로저가 가장 유용하게 쓰이는 상황 중 하나는 현재 상태를 기억하고 있다가 상태가 변경되면 그것을 최신상태로 유지하는 것이다.

<!DOCTYPE html>
<html>
<body>
  <button class="toggle">toggle button</button>
  <div class="txt">
    <h1>toggle test</h1>
  </div>

  <script>
    let txtField = document.querySelector('.txt');
    let toggleBtn = document.querySelector('.toggle');

    let toggle = (function () {
      let isVisable = false;

      // 1.클로저를 반환
      return function () {
        txtField .style.display = isVisable ? 'block' : 'none';
        // 3. 상태 변경
        isVisable = !isVisable;
      };
    })();

    // 2. 이벤트 프로퍼티에 클로저를 할당
    toggleBtn.onclick = toggle;
  </script>
</body>
</html>

위의 예제는 간단하게 toggle 버튼을 통해 텍스트가 보였다가 안보였다가하는 예제인데,
결과는 아래처럼 나온다.

&amp;lt;텍스트 보임&amp;gt;
&amp;lt;클릭후 텍스트 안보임&amp;gt;

위의 코드에서 보면

1.즉시 실행함수가 있는데 이는 함수를 반환한 뒤 바로 소멸된다. 이때 반환된 힘수는 자신이 생성됐을때의 Lexical 환경에 속한 변수인 isVisable을 기억하는 클로저가 된다. 클로저가 기억하는 변수인 isVisable은 txt 표시 상태를 나타낸다.
2.클로저를 버튼 이벤트 프로퍼티에 할당을 했다. 이제 이 클로저를 제거하지 않는한 isVisable 은 소멸되지 않는다.
3. 버튼 클릭시 클로저가 호출되게되는데 이때 txt요소의 표시 상태를 나타내는 변수인 isVisable 값이 변경이 되게 된다. 이 변수는 클로저에 의해 참조 되고 있기 때문에 변경된 최신 값을 계속 유지할 수가 있다.

데이터 은닉화


보통 프로그래밍 초보자들은 전역변수를 통해서 공유될 변수를 작성하는데, 이렇게하면 오류가 발생할 확률이 매우 높아지게 된다. 그 이유는 누구든 이 전역변수에 접근할 수 있기 때문에 의도치 않게 값이 변경될 여지가 많다. 이는 심각한 장애로 이어질 수도 있다.

클로저를 이용하면 이러한 문제를 단번에 해결할 수가 있다.

<!DOCTYPE html>
<html>
  <body>
  <button id="plus">+</button>
  <p id="count">0</p>
  <script>
    let plusBtn = document.getElementById('plus');
    let countTxt = document.getElementById('count');

    let plus= (function () {
      // 카운트 상태를 유지하기 위한 자유 변수
      let count = 0;
      // 클로저를 반환
      return function () {
        return count++;
      };
    }());

    plusBtn.onclick = function () {
      countTxt .innerHTML = plus();
    };
  </script>
</body>
</html>

위의 예시는 숫자를 하나씩 카운트하는 코드인데, 여기서 count라는 변수가 만약 전역변수로 선언되어서 카운팅되게 만들어졌다면 이는 굉장히 위험한 프로그램이 될 것이다.

그런데 위처럼 클로저를 이용해 내부로 철저히 숨김으로써 데이터를 은닉화 시켰다.
객체지향 언어에서 private키워드를 이용해 캡슐화 시키는 것처럼 javascript에서도 클로저의 특징을 이용해 비슷한 기능을 구현할 수 있는 것이다.

실무에서는 더욱더 코드를 안전하게 짜야하기에 클로저를 잘 이용하곤 하니 잘 기억해두도록 하자.

그럼 오늘 포스팅은 여기서 마치도록하겠다. 끝.

끝.

반응형

댓글