📝

12. 클로저

 

12-1. 클로저란

클로저는 내부 함수가 외부 함수에 접근할 수 있는 함수를 의미한다. 함수와 함수가 선언된 어휘적 환경(lexical environment)의 조합을 통해 만들어진다. 함수를 어디서 정의했는지에 따라 상위 스코프를 결정하고 중첩된 함수는 외부 함수 스코프를 상위 스코프로 포함하고 있기 때문에 내부 함수는 외부 함수의 변수의 접근할 수 있다.[1] 반대로 외부 함수에서 내부 함수로 접근하는 것은 불가능하다. 클로저는 자바스크립트에서 중요한 개념이다. 클로저를 사용하면 특정한 상태를 기억하고 값을 은닉할 수 있고, 클로저를 활용하여 모듈 패턴을 정의할 수 있다.
 
💡
"lexical"이란, 어휘적 범위 지정(lexical scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다. 단어 "lexical"은 이런 사실을 나타낸다. 중첩된 함수는 외부 함수 범위(scope)에서 선언한 변수에 내부 함수가 접근할 수 있다. [1]
 

12-1-1. 클로저와 함수의 내부 슬롯 [[Enviroment]]

자바스크립트 엔진은 함수 호출 위치가 아니라 함수 정의 위치에 따라 상위 스코프를 결정한다.
함수는 생성되는 시점에서 자신의 내부 슬롯에 자신이 정의된 환경의 상위 스코프 참조를 저장한다. 함수에서 변수에 접근할 때 함수 안에서 변수를 찾지 못하면 상위 스코프로 확장해서 찾는다. 중첩된 함수는 외부 함수 스코프를 내부 슬롯에 저장한다.
 
const a = 10; function fn() { const a = 5; fnA(); // 함수의 호출 위치와 상위 스코프는 관계가 없다. } function fnA () { console.log(a); // fnA의 상위 스코프는 전역 렉시컬 환경을 내부슬롯에 저장한다. } fn(); // 10 fnA(); // 10
 
아래 예제와 비교해보자.
 
const a = 10; function fn() { const a = 5; function fnA () { console.log(a); } fnA(); } fn(); // 5
 
아래 사이트에서 어떻게 10을 출력하지 않고 5를 출력하는지 확인해보자.

12-1-2. 클로저와 어휘적 환경(lexical environment)

 
function outerFn(){ const x = 'Hello'; const innerFn = function() { console.log(x); } // 클로저 return innerFn(); } const inner = outerFn(); inner();
 
위 예제를 보면 outerFn 함수를 호출했을 때 outerFn 함수는 중첩 함수 innerFn()을 반환한다. 그리고 outerFn 함수가 종료되고 outerFn의 실행 컨텍스트는 제거된다. 일반적으로 실행 컨텍스트가 제거되면 실행 컨텍스트에서 가리키고 있던 어휘적 환경(lexical environment)에 대한 참조가 사라지면서 이 어휘적 환경도 가비지 컬렉션에 의해 함께 사라지게 된다. 하지만 지금의 경우에는 inner 변수가 반환되는 innerFn()을 가리키게 되면서 어휘적 환경에 대한 참조가 살아나게 되고 이로 인해서 innerFn()innerFn() 안에서 참조하고 있는 외부 함수의 x 값도 사용이 가능하게 된다.
이처럼 중첩 함수가 외부 함수보다 더 오래 유지되는 될 때 외부 함수의 변수를 참조하고 있는 함수를 클로저라고 한다.
 
알잘딱깔센 javascript알잘딱깔센 javascript
알잘딱깔센 javascript
 
 
💡
자바스크립트와 같은 고수준 언어들은 "가비지 컬렉션(GC)"이라는 자동 메모리 관리 방법을 사용한다. 가비지 컬렉션의 목적은 메모리 할당을 추적하고 할당된 메모리 블록이 더 이상 필요하지 않게 되었는지를 판단하여 회수하는 것이다.[2]
 
💡
호출 스택, 실행 컨텍스트, 어휘적 환경 실행 컨텍스트란 코드 실행에 필요한 정보들을 저장하고 있는 객체를 나타내며 일반적으로 함수 단위로 이러한 실행 컨텍스트를 가진다. JS 엔진은 함수를 실행할 때 실행 컨텍스트를 생성하여 실행할 함수에 필요한 정보를 저장 및 갱신하며 이 정보들을 이용하여 코드를 실행한다. 여기서 실행 컨텍스트 내부에서 변수나 참조에 대한 정보를 저장하고 있는 것을 렉시컬 환경이라 하며 이 역시 객체의 형태로 존재한다. 호출 스택이란 현재 어떤 함수가 동작하고 있는지, 그 함수 내에서 어떤 함수가 동작하는지, 다음에 어떤 함수가 호출되어야 하는지 등을 제어하는 기능을 담당하며 호출 스택 내부에서는 실행 컨텍스트가 쌓이고 제거되고가 반복된다. JS 엔진을 통해 함수가 실행될 때 생성된 실행 컨텍스트가 쌓이고 종료됐을 때 제거된다.[3]
 

12-1-3. 클로저와 일반함수

위에서 클로저는 중요한 개념이라고 설명하였다. 클로저는 특정 상태를 기억하고 의도치 않게 외부에서 상태가 변경되지 않도록 값을 안전하게 은닉하고 특정 함수를 사용할 때만 변경이 가능하도록 한다. 함수를 호출할 때마다 1씩 증가하여 출력하는 함수를 만들어보자.
 
let number = 1; function increaseNumber() { return number++; } console.log(increaseNumber()); // 1 console.log(increaseNumber()); // 2 console.log(increaseNumber()); // 3 number = 10 console.log(increaseNumber()); // 10
 
숫자가 1씩 증가하는 함수를 만들었을 때 예시처럼 함수를 호출할 때마다 숫자를 증가할 수 있게 된다. 하지만 number라는 변수에 값을 재할당을 하면 증가했던 값은 사라지고 재할당된 값이 남게 된다. number 변수는 전역변수여서 어디서든 접근이 가능해 재할당 시 값이 변하게 된다. 의도치 않은 문제가 발생할 수 있는 코드이다.
 
전역변수 numberincreaseNumber 함수의 지역변수로 한다면 1씩 증가하는 값이 출력되는지 확인해 보자.
 
function increaseNumber() { let number = 1; return number++; } console.log(increaseNumber()); // 1 console.log(increaseNumber()); // 1 console.log(increaseNumber()); // 1 // number = 10 // 함수 블록 스코프에 있는 지역변수라 접근 할 수 없다.
 
전역에서 접근할 수 없게 되어 값이 재할당 되진 않지만 함수를 호출해서 출력하면 1씩 증가하는 것이 아닌 1이 출력이 된다. 1이 나오는 이유는 함수가 실행될 때 생성된 컨텍스트는 함수가 종료된 후 생성된 컨텍스트가 사라진다. 함수 안의 있는 지역 변수는 함수 실행 후 사라지게 된다. 호출할 때마다 변수가 다시 선언되기 때문에 1씩 증가될 수 없고 1이라는 값만 나온다.
 
function increaseNumber() { let number = 1; return function saveVal() { return number++; } } const result = increaseNumber(); console.log(result()); // 1 console.log(result()); // 2 console.log(result()); // 3 console.log(result()); // 4
 
increaseNumber 함수의 return은 값이 아닌 함수를 리턴한다. 내부 함수가 외부 함수 변수의 접근하여 호출할 때마다 증가한 값을 반환한다. 반환된 함수를 연속해서 호출할 수 있도록 변수(result)에 저장한다. result를 호출할 때마다 값이 1씩 증가하는 결과가 출력된다. 이처럼 클로저를 사용하면 특정 상태를 기억하고 은닉할 수 있고 특정 함수에서만 상태를 변경할 수 있다.

12-1-4. 클로저의 작동방식

클로저를 사용했을 때 어떻게 값이 보호되며 특정 함수를 사용할 때만 변경이 되는지 디버깅을 통해 알아보자.
 
💡
Visual Studio 디버깅을 사용하여 프로그램의 런타임 동작을 관찰하고 문제를 찾아낼 수 있다.
 
Visual Studio Code 디버깅을 사용하여 클로저가 어떻게 동작하는지 보자. 처음에 result에 브레이크 포인트를 놓고 단위 실행하여 확인해 보자. 단위를 실행하면 result 안에 increaseNumber 안에 return 함수 saveVal가 들어간다.
 
notion imagenotion image
 
다시 한번 호출을 하면 increaseNumber가 아닌 클로저라는 공간에 number가 저장된 것을 볼 수 있다.
 
notion imagenotion image
 
확인 후 다시 한번 호출을 하면 클로저에 number가 증가되는 것을 볼 수 있다. 처음에 만들어진 클로저에 saveVal 반환값이 증가하는 것이라 변경된 것을 확인할 수 있다. 클로저는 이러한 방식으로 작동된다.
 
notion imagenotion image
 

12-2. 클로저 사용 방법

그래서 이렇게 배운 클로저 어떻게 써야 할지, 이제 알아보도록 하자.
 

12-2-1. 클로저를 통한 JavaScript의 private

12-2-1-1. private

JavaScript에서는 자체적으로 private 기능이 없기 때문에 클로저의 특징을 이용해서 private 기능이 동작하는 것처럼 코드를 작성할 수 있다. 최근 ES2019에서는 private 키워드가 도입되면서 클래스에서 private 기능을 이용할 수 있지만 클래스를 사용하지 않는 경우에는 클로저를 이용하는 방법으로 private 기능을 사용할 수밖에 없다. JavaScript에서 클로저로 어떻게 private 기능을 구현하는지 알아보자. MDN에서는 private 기능에 대해 다음과 같이 얘기하고 있다.
 
자바와 같은 몇몇 언어들은 메소드를 프라이빗으로 선언할 수 있는 기능을 제공한다. 이는 같은 클래스 내부의 다른 메소드에서만 그 메소드들을 호출할 수 있다는 의미이다. [4]
 
JavaScript의 클로저를 통해 구현한 private 기능도 이 개념에서 크게 벗어나지 않는다. 다음 예제들을 통해서 클로저가 private을 어떻게 구현하고 있는지 살펴보자.
 

12-2-1-2. private variable 만들기

클로저에서 private한 변수를 만들기 위해서 일반적으로 아래와 같은 방법을 사용한다. 원리는 12-1-2. 클로저와 어휘적 환경(lexical environment) 코드에서 봤던 설명했던 원리와 동일하다.
 
function makeCounterMethod() { let privateCounter = 0; // 클로저 return function() { privateCounter += 1; console.log(privateCounter); } } const plusCounter = makeCounterMethod(); // ↓ makeCounterMethod 함수는 이미 종료된 상태 ↓ // console.log(privateCounter); // Uncaught ReferenceError: privateCounter is not defined plusCounter(); // 1 plusCounter(); // 2 plusCounter(); // 3
 
일반적인 경우라면 makeCounterMethod함수를 실행하고 종료되었을 때 privateCounter의 실행 컨텍스트 즉 makeCounterMethod의 함수의 스코프가 끝나게 되면서 더 이상 외부에서 privateCounter에 접근할 수 없다.[5] 여기서 더 이상 접근하게 될 수 없게 된 privateCounter가 private한 변수가 된다. 그리고 makeCounterMethod 은 종료 전에 private한 변수를 핸들링 할 수 있는 public 함수(plusCo unter)를 반환해 주는데 이 함수를 통해 접근 불가능하던 private 변수 privateCounter에 접근 가능하도록 만들어준다. 이렇게 private한 변수를 핸들링하기 위해 반환되는 public한 함수를 previleged method라고도 부르며 이러한 방식으로 클로저를 사용하는 것을 모듈 패턴이라고 한다.
 
💡
모듈 패턴? 간단히 말하면 외부에서 내부의 상태나 동작에 접근하지 못하도록 만드는 패턴을 뜻한다. 더 자세한 내용은 아래의 링크를 통해 학습할 수 있다. [6]
 

12-2-1-3. private variable를 여러가지 함수로 조작하기

12-2-1-2. private variable 만들기 경우는 private 변수에 접근할 수 있는 함수가 한 개인 경우에 해당한다. 이번에는 다음 예제를 통해서 한 개의 private 변수를 조작하는 함수가 여러 개인 경우를 살펴보겠다. private 변수 privateCounter에 어떻게 여러 함수가 접근하는지 알아보자.
function makeCounterMethod() { let privateCounter = 0; // 클로저 묶음 객체 return { plusCounter : function() { privateCounter += 1; console.log(privateCounter); }, minusCounter : function() { privateCounter -= 1; console.log(privateCounter); }, getter : function() { return privateCounter; }, setter : function(value) { privateCounter = value }, } } const counterMethod = makeCounterMethod(); // ↓ makePrivateCounter 함수는 이미 종료된 상태 ↓ console.log(privateCounter); // Uncaught ReferenceError: privateCounter is not defined counterMethod.plusCounter(); // 1 counterMethod.minusCounter(); // 0 console.log(counterMethod.getter()); // 0 counterMethod.setter(3); console.log(counterMethod.getter()); // 3
 
위와 같이 private 변수와 관련된 함수들을 객체로 감싼다면 한 개의 private한 변수와 관련된 여러 개의 함수를 한번에 반환하여 사용할 수도 있다. 코드의 결과는 12-2-1-2. private variable 만들기의 경우와 유사하다. 차이가 있다면 makeCounterMethod 함수의 반환 값이 함수를 반환하는지 객체를 반환하는지의 차이이다. 위와 같이 private한 변수를 제어하는 함수들이 여러 개가 필요한 경우에는 이들을 객체로 묶어서 유용하게 사용할 수 있다.
 

12-2-2. 커링 기법에서의 클로저

커링 기법이란 여러 인수를 취하는 함수를 각각 단일 인수를 취하는 일련의 함수로 변환하는 기술이다.[7] JavaScript에서는 커링 기법을 구현하는 데 있어서 클로저를 사용하는데 다음 예제를 통해서 커링 기법의 예시와 그 안에서 클로저를 어떻게 활용하고 있는지 알아보자.
 
function GuGuDan(a, b) { console.log(a * b); }
여러 인수를 취하는 함수
 
위 함수는 두 개의 인수를 취해 구구단을 출력하는 함수이다. 커링 기법을 통해서 각각 단일 인수를 취하는 일련의 함수로 변환할 수 있으며 변환된 결과는 아래와 같다.
 
// 커링 기법을 통해 변환된 함수 function GuGuDan(a) { return function(b) { console.log(a * b); }; } // a의 인자 값이 고정된 함수 생성 const GuGu2 = GuGuDan(2); // private 변수의 값으로 2를 넘겨준다. // GuGuDan함수는 종료 되었지만 매개 변수 a에는 접근 가능하다.(클로저) GuGu2(1); // 2 GuGu2(2); // 4 GuGu2(3); // 6 GuGu2(4); // 8 GuGu2(5); // 10 GuGu2(6); // 12 GuGu2(7); // 14 GuGu2(8); // 16 GuGu2(9); // 18
커링 기법을 통해 단일 인수를 취하는 일련의 함수로 변환
 
이처럼 커링 기법을 이용하여 함수를 변환하면 변환된 함수를 이용해 인자 값이 고정된 함수(GuGu2)를 만들어 낼 수 있다. 이 말은 특정 인자를 재사용함으로써 반복되는 코드를 작성하지 않아도 된다는 뜻이며 이 점은 코드 재사용성 측면에서 유용하다고 볼 수 있다.
위 코드의 경우 GuGuDan함수를 통해 GuGu2라는 인자 값이 2로 고정된 함수를 생성한다. 코드에서 변환된 함수(GuGudan)을 보면 함수는 종료되었지만 GuGuDan 함수가 반환한 익명 함수를 변수 GuGu 2가 가리키고 있다. GuGu2는 이 익명 함수를 통해서 a값에 접근이 가능하기 때문에 이 경우도 클로저를 이용하여 구현되었다고 볼 수 있다.
이처럼 커링 기법은 함수 변환 과정에서 클로저를 생성하고 이를 이용하여 인자 값이 고정된 함수를 만들어 낼 수 있다.
 

12-2-3. 반복문에서의 클로저

12-2-3-1. 흔히 발생하는 실수

코드를 작성하는 데 있어 반복문과 클로저를 함께 사용되는 경우 코드가 의도한 대로 동작하지 않는 문제가 발생할 수 있다. 다음 코드는 콘솔에 각 구구단 버튼에 해당하는 구구단 연산 결과를 출력하는 페이지이다. 각 버튼이 눌렸을 때 어떤 결과를 출력하는지 알아보고 왜 이런 결과가 출력되었는지 생각해 보자.
 
<!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>Closure</title> </head> <body> <h1>구구단</h1> <button>콘솔에 2단 출력</button> <button>콘솔에 3단 출력</button> <button>콘솔에 4단 출력</button> <button>콘솔에 5단 출력</button> <button>콘솔에 6단 출력</button> <button>콘솔에 7단 출력</button> <button>콘솔에 8단 출력</button> <button>콘솔에 9단 출력</button> <!-- JavaScript 코드 --> <script> </script> </body> </html>
HTML 코드
const buttons = document.querySelectorAll("button"); function printGuGuDan(i) { for (var j = 1; j < 10; j++) { console.log(i * j); } } function makeGuGuDan() { // makeGuGuDanBtn()의 private 변수 i for (var i = 2; i < 10; i++) { // private 변수 i를 사용하는 익명 함수 function(){...} // 익명 함수를 가리키는 프로퍼티 onclick buttons[i - 2].onclick = function () { printGuGuDan(i) }; } } makeGuGuDan();
JavaScript 코드
 
출력의 결과는 어떤 버튼을 눌러도 아래의 결과와 같다.
10 20 30 40 50 60 70 80 90
버튼을 누르면 출력되는 구구단 10단
 
어째서 이런 일이 발생하는 것일까? 코드를 보며 문제를 하나씩 짚어보자.
 
function printGuGuDan(number) { for (var i = 1; i < 10; i++) { console.log(number * i); } } function makeGuGuDan() { for (var i = 2; i < 10; i++) { buttons[i - 2].onclick = function () { printGuGuDan(i) }; } } makeGuGuDanBtn();
문제가 발생하는 코드
 

12-2-3-2. 실수가 발생하는 원인 1 : onclick 프로퍼티를 통한 클로저 생성

우선 문제를 들여다보면 위의 코드에선 makeGuGuDan 함수가 실행되고 실행되는 과정에서 button 요소들의 onclick 프로퍼티에 private 변수 i를 사용하는 익명 함수가 바인딩 되고 makeGuGuDan 함수가 종료된다. 이로써 브라우저에서 button 요소를 클릭할 경우 종료된 makeGuGuDan 함수 외부에서 button.onclick 프로퍼티에 바인딩 된 익명 함수를 통해 makeGuGuDan() 내부의 변수 i에 접근할 수 있다. button.onclick 을 통해서 종료된 makeGuGuDan의 컨텍스트 정보를 참조하고 있는 모습은 클로저와 같다.
 
알잘딱깔센 javascript : onclick 프로퍼티를 통한 클로저 생성알잘딱깔센 javascript : onclick 프로퍼티를 통한 클로저 생성
알잘딱깔센 javascript : onclick 프로퍼티를 통한 클로저 생성
 

12-2-3-3. 실수가 발생하는 원인 2 : 클로저, var, 반복문

문제는 클로저와 var 변수 그리고 반복문이 함께 사용되면서 발생한다. 이 경우 각 버튼의 onclick 프로퍼티를 이용해서 종료된 makeGuGuDan의 컨텍스트 정보를 참조하지만 makeGuGuDan은 한번만 호출됨으로 한 개의 실행 컨텍스트만을 생성한다. 이 말은 모든 버튼의 onclick 프로퍼티가 하나의 컨텍스트 정보의 i, 즉 모두 같은 i를 참조하게 된다는 말과 같다.
 
알잘딱깔센 javascript : 클로저, var, 반복문알잘딱깔센 javascript : 클로저, var, 반복문
알잘딱깔센 javascript : 클로저, var, 반복문
 
let과 같은 블록 레벨 스코프를 따르는 키워드를 사용했다면 for 문이 돌 때마다 한 개의 컨텍스트가 생성되어 각각의 컨텍스트 정보에 있는 각각의 i값을 참조했겠지만 var 키워드는 함수 레벨 스코프를 따르기 때문에 for 문에서는 별개의 스코프를 생성하지 않고 별개의 컨텍스트도 생성되지 않는다. 이와 같은 이유로 모든 버튼이 같은 i 값을 가리키게 된다.
 
10 20 30 40 50 60 70 80 90
실수에 의해 출력되는 구구단 10단
 

12-2-3-4. 실수 바로잡기

위와 같은 실수의 핵심은 클로저를 통해 가리키는 변수가 모두 같은 컨텍스트 내용의 변수를 가리킨다는 것에 있다. 그렇기 때문에 이러한 문제를 해결해 주기 위해선 반복문을 통해 생성되는 클로저가 공통된 컨텍스트 내 변수 i가 아닌 별개의 컨텍스트 내 변수 i를 참조하게 해주어야 한다. 다음으로 제시하는 방법들을 사용하면 이와 같은 문제를 해결할 수 있다. 하나하나 살펴보며 직면한 문제를 해결해 보자.
 
1. 클로저 추가하기
<!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>Closure</title> </head> <body> <h1>구구단</h1> <button>콘솔에 2단 출력</button> <button>콘솔에 3단 출력</button> <button>콘솔에 4단 출력</button> <button>콘솔에 5단 출력</button> <button>콘솔에 6단 출력</button> <button>콘솔에 7단 출력</button> <button>콘솔에 8단 출력</button> <button>콘솔에 9단 출력</button> <!-- JavaScript 코드 --> <script> </script> </body> </html>
HTML 코드
const buttons = document.querySelectorAll("button"); function printGuGuDan(number) { for (var j = 1; j < 10; j++) { console.log(number * j); } } // 클로저 추가하기 function addClosure(input) { return function () { printGuGuDan(input); }; } function makeGuGuDan() { for (var i = 2; i < 10; i++) { buttons[i - 2].onclick = addClosure(i); } } makeGuGuDan();
JavaScript 코드
 
위 코드는 onclick 프로퍼티addClosure의 반환 값인 익명 함수(내부 함수)를 가리킨다. 또한 이 익명 함수를 통해서 addClosure의 매개 변수 input(private 변수)에 접근할 수 있다. 이 경우 반복문이 돌 때마다 동안 addClosure가 호출되고 JavaScript에서는 함수가 호출될 때 새로운 실행 컨텍스트를 만들기 때문에 private 변수 input은 다 별개의 컨텍스트로 관리된다. 따라서 onclick 프로퍼티는 별 개의 input을 참조할 수 있게 되고 같은 컨텍스트의 변수를 참조하던 문제를 해결할 수 있게 되어 코드가 정상 작동하게 된다. 이와 같이 클로저를 추가하는 방법은 실무에서도 많이 사용되는 방법이니 참고해두면 도움이 될 것이다.
 
알잘딱깔센 javascript : addClosure 클로저 추가하기알잘딱깔센 javascript : addClosure 클로저 추가하기
알잘딱깔센 javascript : addClosure 클로저 추가하기
 
2. 익명 클로저 사용하기
<!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>Closure</title> </head> <body> <h1>구구단</h1> <button>콘솔에 2단 출력</button> <button>콘솔에 3단 출력</button> <button>콘솔에 4단 출력</button> <button>콘솔에 5단 출력</button> <button>콘솔에 6단 출력</button> <button>콘솔에 7단 출력</button> <button>콘솔에 8단 출력</button> <button>콘솔에 9단 출력</button> <script> </script> </body> </html>
HTML 코드
const buttons = document.querySelectorAll("button"); function printGuGuDan(number) { for (var i = 1; i < 10; i++) { console.log(number * i); } } function makeGuGuDan() { for (var i = 2; i < 10; i++) { // 익명 클로저 추가 (function (input) { buttons[input - 2].onclick = function () { printGuGuDan(input) }; })(i); } } makeGuGuDan();
JavaScript 코드
 
이 경우는 반복문 안에서 기존의 코드를 즉시 실행 함수로 감싸서 즉시 실행 함수가 실행될 때마다 새로운 컨텍스트를 만들도록 한 방법이다. 각 컨텍스트에서는 for 문에 의해 변하는 각각의 i에 해당하는 값을 input이 가지게 되며 별개로 관리되게 된다.
 
알잘딱깔센 javascript : 익명 클로저 추가하기알잘딱깔센 javascript : 익명 클로저 추가하기
알잘딱깔센 javascript : 익명 클로저 추가하기
 
3. let 키워드 사용하기
<!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>Closure</title> </head> <body> <h1>구구단</h1> <button>콘솔에 2단 출력</button> <button>콘솔에 3단 출력</button> <button>콘솔에 4단 출력</button> <button>콘솔에 5단 출력</button> <button>콘솔에 6단 출력</button> <button>콘솔에 7단 출력</button> <button>콘솔에 8단 출력</button> <button>콘솔에 9단 출력</button> <script> </script> </body> </html>
HTML 코드
const buttons = document.querySelectorAll("button"); function printGuGuDan(number) { for (var i = 1; i < 10; i++) { console.log(number * i); } } function makeGuGuDan() { // let 키워드 사용 for (let i = 2; i < 10; i++) { buttons[i - 2].onclick = function () { printGuGuDan(i) }; } } makeGuGuDan();
JavaScript 코드
 
JavaScript에서 let은 블록 레벨 스코프를 따르기 때문에 for 문을 돌면서 블록({})을 만날 때마다 새로운 실행 컨텍스트를 생성하게 된다. 따라서 이 경우도 그림으로 나타내면 위의 두 경우와 동일하다.
 
알잘딱깔센 javascript : let 키워드 사용하기알잘딱깔센 javascript : let 키워드 사용하기
알잘딱깔센 javascript : let 키워드 사용하기
 
이와 같은 방법들은 모두 클로저가 가리키는 컨텍스트 정보를 분리해 줌으로써 서로 각각의 변수 i를 가리킬 수 있게 하여 반복문과 var 변수를 사용해서 클로저가 생성되었을 때 문제를 해결할 수 있다.

Reference


  1. https://developer.mozilla.org/ko/docs/Glossary/Scope
  1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Memory_Management#garbage_collection
  1. https://developer.mozilla.org/ko/docs/Glossary/Call_stack
  1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures
  1. https://developer.mozilla.org/ko/docs/Web/JavaScript/Closures#클로저closure
  1. https://en.wikipedia.org/wiki/Module_pattern#JavaScript
  1. https://en.wikipedia.org/wiki/Currying