Dandy Now!
  • [JavaScript] 클로저(Closure) 정리!
    2023년 09월 26일 00시 38분 06초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    1. 클로저(Closure) 란?

    클로저는 함수와 함수가 선언된 렉시컬 스코프(Lexical Scope) 사이의 특별한 관계를 나타내는 개념이다. 함수 내부에서 선언된 함수가 외부 함수의 변수에 접근할 수 있고, 그러한 함수를 클로저라고 한다. 클로저는 외부 함수의 변수에 대한 참조를 "닫아"(Capture)서 나중에 사용할 수 있도록 저장한다. 이로 인해 외부 함수가 종료된 이후에도 해당 변수에 접근할 수 있다.

     

    2. 클로저와 관련한 몇 가지 개념

    1) 렉시컬 스코프(Lexical Scope): JavaScript는 함수를 정의할 때 함수 내부에서 외부 스코프에 접근할 수 있도록 스코프를 정의한다. 이 스코프는 함수를 선언할 때 결정되며, 함수가 어떤 변수에 접근할 수 있는지 결정한다. 이러한 스코프 규칙을 렉시컬 스코프라고 한다. 렉시컬 스코프는 코드를 작성할 때 정적으로 결정된다.

    2) 실행 컨텍스트(Execution Context): 실행 컨텍스트는 코드가 실행될 때 생성되며 현재 실행 중인 코드 블록에 대한 정보를 담고 있다. 실행 컨텍스트에는 변수, 함수, 스코프 체인 등의 정보가 포함된다.

    3) 스코프 체인(Scope Chain): 실행 컨텍스트마다 해당 컨텍스트의 변수와 외부 컨텍스트(상위 스코프)에 대한 참조를 가지고 있다. 이것이 스코프 체인이며, 변수를 검색할 때 사용된다. 스코프 체인을 통해 렉시컬 스코프가 구현된다.

     

    3. 실행 컨텍스트와 스코프 체인에 의한 동적 스코프 조작과 클로저

    • 렉시컬 스코프는 함수를 정의할 때 결정된다. 이것은 함수가 어떤 스코프에 접근할 수 있는지를 정의하는 데 사용된다.
    • 실행 컨텍스트와 스코프 체인은 런타임 중에 생성된다. 코드가 실행될 때마다 실행 컨텍스트가 생성되고, 해당 컨텍스트의 스코프 체인이 형성된다.
    • 이러한 동적 스코프 체인은 함수가 런타임 중에 동적으로 다른 스코프에 접근할 수 있는 유연성을 제공한다.
    • 예를 들어, 함수가 다른 함수 내부에서 실행될 때 해당 함수의 스코프에 접근할 수 있게 된다.
    • 이러한 동적 스코프 체인은 함수가 다른 함수 내에서 동작할 때 필요한 변수 및 데이터에 접근할 수 있도록 한다. 이것은 클로저가 가능한 이유 중 하나이다.
    • 따라서 JavaScript는 함수의 스코프를 정적으로 결정하지만, 실행 컨텍스트와 스코프 체인을 통해 런타임 중에 동적으로 스코프를 조작할 수 있다.
    • 이것은 렉시컬 스코프와 동적 스코프 체인을 혼합하여 언어의 유연성을 제공하며, 클로저와 같은 고급 기능을 가능하게 한다.

     

    4. 클로저 예제

    4.1. 예제 1

    function outer() {
      let i = 0;
      function inner(x) {
        i += x;
        return i;
      }
      return inner;
    }
    
    // a와 b는 독립적으로 존재한다!
    const a = outer();
    const b = outer();
    
    console.log("a: ", a(1)); // a:  1
    console.log("b: ", b(10)); // b:  10
    console.log("a: ", a(1)); // a:  2
    console.log("b: ", b(10)); // b:  20

     

    4.2. 예제 2 : setTimeout()

    // 1초 후 10 출력
    var i;
    for (i = 0; i < 10; i++) {
      setTimeout(() => console.log(i), 1000);
    }
    
    // 1초 후 0~9까지 출력
    var i;
    for (i = 0; i < 10; i++) {
      ((j) => setTimeout(() => console.log(j), 1000))(i);
    }
    
    // 1초 후 0~9까지 출력
    // let 키워드 사용시 즉시 실행함수를 적용하지 않아도 0~9 출력하고 있음
    for (let i = 0; i < 10; i++) {
      setTimeout(function () {
        console.log(i);
      }, 1000);
    }
    
    // 1초 후 0~9까지 출력
    var i;
    for (i = 0; i < 10; i++) {
      (function (j) {
        setTimeout(function () {
          console.log(j);
        }, 1000);
      })(i);
    }

     

    4.3. 예제 3 : 클로저로 인해 정상 작동하지 않는 코드와 개선된 코드

    <!DOCTYPE html>
    <html lang="ko">
    
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
    </head>
    
    <body>
        <p id="help">Helpful notes will appear here</p>
        <p>E-mail: <input type="text" id="email" name="email"></p>
        <p>Name: <input type="text" id="name" name="name"></p>
        <p>Age: <input type="text" id="age" name="age"></p>
        <script>
        
            // 참고:  https://jake-seo-dev.tistory.com/182
    
            function showHelp(help) {
                document.getElementById('help').innerHTML = help;
            }
    
            function setupHelp() {
                var helpText = [
                    { 'id': 'email', 'help': 'Your e-mail address' },
                    { 'id': 'name', 'help': 'Your full name' },
                    { 'id': 'age', 'help': 'Your age (you must be over 16)' }
                ];
    
                // 1. 클로저로 인해 정상 작동하지 않는 코드
                for (var i = 0; i < helpText.length; i++) {
                    var item = helpText[i];
                    document.getElementById(item.id).onfocus = function () {
                        showHelp(item.help);
                    }; // 익명 함수 자체가 할당 되고 있음
                }
    
                // 2. 정상 작동하는 개선된 코드
                // 1) 즉시 실행 함수
                // for (var i = 0; i < helpText.length; i++) {
                //     var item = helpText[i];
                //     document.getElementById(item.id).onfocus = (
                //         function (txt) {
                //             return function () {
                //                 return showHelp(txt);
                //             }
                //         })(item.help) // 즉시 실행 함수로 "리턴 값 할당"
                // }
    
                // 2) 즉시 실행 함수(화살표 함수)
                // for (var i = 0; i < helpText.length; i++) {
                //     var item = helpText[i];
                //     document.getElementById(item.id).onfocus = ((txt) => () => {
                //         showHelp(txt);
                //     })(item.help); // 즉시 실행 함수로 "리턴 값 할당"
                // }
    
                // 3) result 함수 호출
                // for (var i = 0; i < helpText.length; i++) {
                //     var item = helpText[i];
                //     document.getElementById(item.id).onfocus = result(item.help) // result함수 리턴 값을 할당
                // }
                // function result(txt) {
                //     return function () {
                //         return showHelp(txt);
                //     }
                // }
    
            }
    
            setupHelp();
        </script>
    </body>
    
    </html>

     

    참고 자료
    https://jake-seo-dev.tistory.com/182
    728x90
    반응형
    댓글