JS의 Lexcal scoping 과 클로저(Closure)

horiz.d·2021년 12월 15일
0

JS 꿀단지

목록 보기
15/35

클로저

클로저는 [ 함수 & 함수가 선언된 어휘적 환경 ] 의 조합이다. 따라서 클로저를 이해하기 위해서는 Javascript가 어떻게 변수의 유효범위를 지정하는 지, Lexical scoping,를 먼저 이해해야 할 것이다.


Lexical Scoping

우리말론 '어휘적 범위지정' 이라고 한다.

내장함수(inner function)은 자신의 부모함수의 인자에 접근하고 이를 활용할 수 있다. 아래의 코드를 보자.

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

init()은 지역변수 name과 내장함수 displayName()을 생성한다. 내장함수인 displayName()은 자신만의 지역변수가 없지만, 위에서 밝힌 내장함수의 특성대로 자신의 부모함수의 인자를 자신의 내부에서 활용했다.

만약 displayNmae()이 자신만의 name변수를 가졌다면, name이 아니라 this.name을 사용했을 것이란 사실에 주의하자.


위의 예시는 함수가 중첩된 상황에서 Parser가 어떻게 변수를 처리하는 지 확인해 볼 수 있었다. 이는 Lexical scoping의 하나의 예시였다.

Lexical Scoping, 이쯤에서 우리는 Lexical(어휘적) 이라는 명칭으로 "값(변수/함수)이 어디에서 사용 가능한지 알기 위해 값이 소스코드 내의 어느 위치에서 선언되었는지 고려된다는 사실"을 알 수 있다.

또한 Inner function은 외부 스코프( 위에선 자신의 모함수 )의 값을 활용할 수 있다는 사실도 확인했다.


클로저(Closure)

아래의 예시코드를 보자.

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

위의 코드는 직전의 코드와 동일한 결과를 실행시킨다.

이 코드에서 알 수 있는 매우 흥미로운 사실은, 내장함수인 displayName이 makeFunc()를 통해 myFunc라는 변수에 반환, 저장되어 myFunc() 코드가 내장함수인 displayName()을 실행시켰음에도 불구하고, 여전히 자신의 외부스코프의 변수"였던" name변수를 그대로 참조하여 정상적으로 동작을 수행했다는 점이다.

이는 몇몇 프로그래밍 언어에서는 전혀 직관적이지 않은 일인데, 왜냐하면 특정 언어들 에서는 함수 내부의 지역변수들이 함수가 "실행(running)" 상태인 경우에만 존재하기 때문이다. 이런 맥락에서는, makeFunc()의 실행이 끝난 시점인 myFunc()의 실행시점에서는 makeFunc()의 지역변수인 Name에는 접근할 수 없는 것이 상식적일 것이다.


하지만! 자바스크립트는 다르다.
자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하기 때문이다. 클로저는 [함수 & 함수가 선언된 Lexical 환경의 조합] 이라고 앞서 밝혔다.


여기서 말한 함수가 선언된 Lexical 환경은,
클로저가 생성된 시점유효 범위 내에 있는 모든 지역변수 로 구성된다.


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

displayName의 인스턴스는 변수 name이 있는 Lexical환경에 대한 참조를 유지한다. 이런 이유로 myFunc가 호출될 때 변수 name은 사용할 수 있는 상태로 남아있는 것이고 "seongwoo"가 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값에 접근하여 값을 계산

여기서 add5와 add10은 클로저이다. 즉, 함수와 함수의 Lexical환경( 이 예제에서는 익명 내부함수가 생성될 당시의 Lexical환경 )의 조합이다.

이 예시는 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주며, 클로저에 단순히 값 형태로 전달되는 것이 아니라는 것을 의미한다.



클로저의 실전 활용

(1) 상태유지 예제

아래의 javascript (in html) 예제를 보자.

<!DOCTYPE html>
<html>
<body>
  <button class="toggle">toggle</button>
  <div class="box" style="width: 100px; height: 100px; background: red;"></div>

  <script>
    var box = document.querySelector('.box');
    var toggleBtn = document.querySelector('.toggle');

    var toggle = (function () { //익명함수를 toggle변수에 할당
      var isShow = false;		//해당 익명함수의 지역변수이자 내부함수의 외부스코프에 위치한 함수로 내부함수의 Lexical환경의 일부.

      // ① 클로저를 반환
      return function () {		//내부함수이자 부모함수의 지역변수(해당 함수 기준으로는 외부스코프)를 기억하는 클로저
        box.style.display = isShow ? 'block' : 'none';
        // ③ 상태 변경
        isShow = !isShow;
      };
    })();

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

위의 예제에선 익명함수, 내부함수(inner function), 클로저, 즉시호출 개념이 사용되었다.

큰 맥락으로 보면 변수 toggle에 부모(익명)함수의 즉시호출이 할당되었고. 이 toggle변수가 사용될 때 즉시 그 익명함수를 호출할 것이다.

'toggleBtn.onclick = toggle' 에서 토글버튼을 클릭하는 이벤트가 발생할 시, toggle을 할당하여 해당 익명함수를 즉시호출하도록 했는데, 이 것이 호출됨에 따라 내부함수로 존재하는 익명함수 의 클로저를 반환하게 된다. 클로저는 (함수 & 함수의 Lexical 환경 ) 으로 구성되는데, 이 중 Lexical 환경에는 그의 부모함수의 지역변수인 isShow 변수와 그 상태가 포함된다.

따라서, toggle버튼이 클릭되는 이벤트가 발생할 때, Closure이 활용되어 isShow의 상태가 스위칭되며 동시에 그 상태가 기억될 수 있는 것이다.


이외에도 (2) 전역변수 사용억제, (3) 정보의 은닉 등의 예제를 https://poiemaweb.com/js-closure 해당 포스팅에서 참고하기를 추천한다.


REF

MDN - 클로저 :

https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures

poiemaweb - 클로저 실전활용 :

https://poiemaweb.com/js-closure

profile
가용한 시간은 한정적이고, 배울건 넘쳐난다.

0개의 댓글