언어·프레임워크/JavaScript

[JavaScript] 클로저(Closure) 정리!

DandyNow 2023. 9. 26. 00:38
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
반응형