mimizae 님의 블로그
클로저란(Closure)? (2) 본문
이전 글을 읽고 와 주시면 감사하겠습니다! 👍🏻

클로저의 활용
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다.
상태가 의도치 않게 변경되지 않도록 상태를 은닉하고 특정 함수에게만 상태 변경을 허용하게 한다.
함수가 호출될 때마다 호출된 횟수를 누적해 출력하는 카운터 예제를 통해 깊이 이해해 보자.
여기에서는 호출된 횟수가 바로 안전하게 변경하고 유지해야 할 상태이다.
// 카운트 상태 변수
let num = 0;
// 카운트 상태 변경 함수
const increase = function(){
return ++num;
};
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
이 코드는 잘 동작하지만 오류를 발생시킬 가능성이 있다. 왜냐하면 다음 두 전제 조건이 지켜져야 바르게 동작함이 보장되기 때문이다.
(나라도 저렇게 짰을 텐데... ㅠ.ㅠ)
- 카운트 상태, 즉 num 변수는 increase 함수가 호출되기 전까지 변경되지 않고 유지되어야 한다.
- 그렇기에 num 변수는 increase 함수를 통해서만 변경할 수 있어야 한다.
하지만 위의 예제는 카운트 상태 변수가 전역으로 선언되어 누구나 접근할 수 있고 변경할 수도 있다.
이는 의도치 않게 변경될 수 있음을 의미한다!!!!
increase 함수만 접근할 수 있어야 한다면, 전역 변수 num을 increase의 지역 변수로 수정해 볼까?
// 카운트 상태 변경 함수
const increase = function(){
// 카운트 상태 변수
let num = 0;
return ++num;
};
// 이전 상태 유지 불가, let num = 0으로 초기화 되니까!
console.log(increase()); // 1
console.log(increase()); // 1
console.log(increase()); // 1
이제 num 변수는 increase 함수를 통해서만 변경 가능하다.
그러나 increase 함수가 호출될 때마다 지역 변수 num이 다시 선언되고 0으로 초기화 되기 때문에 출력 결과는 언제나 1이다.
즉, 이전 상태를 유지 못한다... 오케이!! 이전 상태를 유지와 의도치 않은 변경을 막기 위해 클로저를 활용해 보자!!!!
// 카운트 상태 변경 함수
const increase = (function(){
// 카운트 상태 변수
let num = 0;
return function(){
return num++;
};
}());
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
이 코드가 실행되면 즉시 실행 함수가 호출되고
(function(){ ... }()); // -> 이 function(){}은 정의되자마자 실행 됨
즉시 실행함수가 반환한 함수
function(){ return num++; };
가 increase에 할당된다! (여기에서 즉시 실행 함수란 정의하자마자 한 번 실행하는 함수를 의미한다.)
increase에 할당된 함수(중첩 함수)는 자신의 상위 스코프인 즉시 실행 함수의 렉시컬 환경을 기억하고 있는 클로저이다!!
즉시 실행 함수는 호출된 이후 바로 소멸하지만 이 함수가 리턴한 중첩 함수는 변수 increase에 할당되어 더 오래 유지되며 상위 스코프인 즉시 실행 함수의 변수 num을 참조하고 있기 때문이다.
이 클로저는 카운트 상태를 유지하기 위한 자유 변수 num을 언제 어디서 호출하든지 참조하고 변경할 수 있다.
즉시 실행 함수는 한 번만 실행되고 종료되기 때문에 num 변수에 접근할 수 없을 것 같지만, 즉시 실행 함수가 반환한 클로저가 이 상위 렉시컬 환경을 기억하고 변수 num을 기억하고 있어 여전히 접근할 수 있다.
// 카운트 상태 변경 함수
const increase = (function(){
// 카운트 상태 변수
let num = 0;
return function(){
return num++;
};
}());
// 즉시 실행 함수가 사라져도
// const increase = 클로저; 는 살아있음! 그래서 num이 계속 ++ 될 수 있는 것이고
// 이 num은 상위 스코프를 참조할 수 있는 클로저인 중첩 함수를 통해서만 접근할 수 있기에 안전함
console.log(increase()); // 1 -> 이 때는 즉시 실행 함수의 실행 결과
console.log(increase()); // 2 -> 여기부터는 클로저가 기억하고 있는 num 변수의 증가 값
console.log(increase()); // 3
이처럼 클로저는 상태가 의도치 않게 변경되지 않도록 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용해 상태를 안전하게 변경하고 유지하기 위해 사용한다!!!
const counter = (function() {
let num = 0;
// 클로저인 메서드들(increase, decrease)를 가지는 객체를 반환
// 객체 리터럴은 스코프를 만들지 않기 때문에
// 각 메서드들을 객체로 감싸고 있지만, 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경이다!
return {
increase(){
return ++ num;
},
decrease(){
return num > 0 ? --num : 0;
}
};
}());
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0
앞선 카운터 예제를 좀 발전 시켜 감소까지 시킬 수 있도록 하는 예제이다.
즉시 실행 함수가 반환하는 객체 리터럴은 즉시 실행함수의 평가 단계에서 평가되어 객체가 된다!!
이때 객체의 메서드들도 함수 객체로 생성된다. 객체 리터럴의 중괄호는 코드 블록이 아니기 때문에 스코프를 형성하지 않는다.
increase, decrease 메서드의 상위 스코프는 두 메서드가 평가되는 시점에 실행 중인 즉시 실행 함수의 실행 컨텍스트의 렉시컬 환경이다!! 따라서, 두 메서드가 어디에서 호출이 되든 상위 스코프의 식별자 num을 참조해 값을 변경할 수 있다.
위 예제를 생성자 함수로 표현해 본다면?!
const Counter = (function (){
let num = 0;
function Counter(){
//this.num = 0; 프로퍼티는 public 하므로 은닉되지 않는다.
}
Counter.prototype.increase = function(){
return ++num;
};
Counter.prototype.decrease = function(){
return num > 0 ? --num : 0;
};
return Counter;
}());
const counter = new Counter();
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.decrease()); // 1
console.log(counter.decrease()); // 0
변수 num은 생성자 함수 Counter가 생성할 인스턴스의 프로퍼티가 아니라 즉시 실행 함수 내의 변수다.
만약!!!! num이 생성자 함수 Counter가 생성할 인스턴스의 프로퍼티라면 (this.num = 0;) 생성된 인스턴스를 통해 외부에서 접근이 자유로운 public 프로퍼티가 된다. (안전하지 못하다 ㅡㅡ;;)
하지만 즉시 실행 함수에서 선언이 되었기에, 인스턴스를 통해 접근할 수 없으며 외부에서도 직접 접근할 수 없는, increase와 decrease 메서드 호출로서만 값이 변경되는 은닉된 변수이다.
// 함수를 인수로 전달 받고 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
function makeCounter(aux){
let counter = 0;
// 클로저를 반환
return function(){
counter = aux(counter);
// 인수로 전달 받은 보조 함수 aux에 상태 변경 권한을 위임한다.
// 클로저를 통해 상태 변경 가능하지만, 이를 전달 받은 보조 함수에 위임해 보조 함수 내부 로직으로 자유 변수 값을 변경할 수 있게끔
// 내부 상태 변경(num 값) 로직을 외부에서(보조 함수) 정의할 수 있도록!!
return counter;
};
}
// 보조 함수
function increase(num){
return ++num;
}
function decrease(num){
return --num;
}
// 함수로 함수를 생성한다
// makeCounter 함수는 보조함수를 인수로 전달 받아 함수를 반환한다.
const increaser = makeCounter(increase);
console.log(increaser()); // ?
console.log(increaser()); // ?
const decreaser = makeCounter(decrease);
console.log(decreaser()); // ?
console.log(decreaser()); // ?
외부 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에서 부수 효과를 최대한 억제해 오류를 피하고 안정성을 높이기 위해 클로저를 적극 활용한다.
위의 예제는 카운터를 고차함수를 활용하는 식으로 나타낸 함수형 프로그래밍 예제이다.
(고차함수란 함수를 인자로 받고, 함수를 반환하는 함수이다!! 사실 고차 함수 잘 안 써봤다... 😅)
makeCounter 고차 함수가 반환하는 함수는 makeCounter의 렉시컬 환경을 기억하는 클로저 함수이다.
makeCounter가 인수로 전달 받은 보조 함수가 자신이 반환하는 함수의 동작을 결정한다!!
(increase가 보조 함수면 +, decrease가 보조 함수면 -...)
여기에서 주목할 점!!!! 위 코드의 console 출력 결과가 몇일까?
정답은...
const increaser = makeCounter(increase);
console.log(increaser()); // 1
console.log(increaser()); // 2
const decreaser = makeCounter(decrease);
console.log(decreaser()); // -1
console.log(decreaser()); // -2
헐 왜 자유 변수 counter가 공유되지 않지??
그 이유는, makeCounter 함수를 호출해서 함수가 반환될 때 그 반환된 함수는 자신만의 독립적인 렉시컬 환경을 갖기 때문이다!!!!
(즉시 생성 함수 예제만 보다가 오니까 헷갈린다;;)
함수를 호출할 때마다 새로운 makeCounter 함수 실행 컨텍스트의 렉시컬 환경이 생성되기 때문이다.
즉시 생성 함수 예제에서는 단 한 번만 실행이 되기에 하나의 렉시컬 환경을 공유했던 것!!

위의 그림과 같이
const increaser = makeCounter(increase);
이렇게 makeCounter 함수를 호출하면 makeCounter의 실행 컨텍스트(전역 실행 컨텍스트)가 생성이 된다. 그리고 makeCounter 함수는 함수 객체를 생성해 반환한 후 소멸된다!!!
(그림은 지금, 실행 컨텍스트 스택에 makeCounter 실행 컨텍스트가 push 됐다가 pop 되어 렉시컬 환경만 남은 상태임!!!)
그럼 makeCounter가 반환한 함수는 makeCounter의 렉시컬 환경을 기억하는 클로저이며
그 환경은 클로저 함수 객체의 내부 슬롯 [[Environment]]에 의해 참조되고 있다.

그런데 여기에서 const decreaser = makeCounter(decrease);으로 makeCounter 함수를 호출했을 때 기존의 렉시컬 환경을 참조하는 게 아니라 새로 생성된 makeCounter의 실행 컨텍스트의 렉시컬 환경을 참조하게 된다...!!!!
이렇게 각각 독립된 렉시컬 환경을 갖기 때문에 자유 변수 counter을 공유하지 않아 상태 연동이 되지 않는다.
렉시컬 환경을 공유하기 위해서는 이렇게 두 번 호출하지 말아야 한다.
그럼 어떻게 해야 할까??
const counter = (function (){
let counter = 0;
return function(aux){
counter = aux(counter);
return counter;
};
}());
function increase(num){
return ++num;
}
function decrease(num){
return --num;
}
console.log(counter(increase)); // ?
console.log(counter(increase)); // ?
console.log(counter(decrease)); // ?
console.log(counter(decrease)); // ?
이렇게 하면 된다! 다시 즉시 실행 함수를 사용하는 방식으로 돌아왔다.
이 코드에서는 즉시 실행 함수를 한 번만 호출했기 때문에, 그 내부의 counter 변수는 단 하나만 생성되고, 모든 counter(…) 호출이 그 하나의 동일한 counter 변수(렉시컬 환경)을 공유한다!!
잠깐!!!! 그렇다면 클로저는 즉시 실행 함수와 항상 함께 써야 하는 건가요? 🤔
항상 즉시 실행 함수를 써야 한다는 건 아니고,
상태를 한 번만 만들고 여러 곳에서 공유하고 싶을 때!!!! 즉시 실행 함수를 쓰는 게 유용한 선택이다.
1. 즉시 실행 함수가 하는 역할 정리...
즉시 실행 함수는 딱 한 번만 실행되고, 그 내부의 상태를 감싸는 스코프를 만들어주는 도구이다.
즉, 싱글톤(Singleton) 형태의 상태를 만들 때 유용하다!!!!!!
예를 들어
const counter = (function () {
let num = 0;
return function () {
return ++num;
};
})();
- 즉시 실행 함수가 딱 한 번만 실행 됨 → num이라는 스코프 하나!!!!!!! 생성
- 반환된 내부 함수가 그 스코프를 공유
- 그래서 counter()를 여러 번 호출해도 같은 num을 사용
이처럼 즉시 실행 함수 = 상태를 한 번만 생성하고, 그 상태를 계속 유지하고 싶을 때 적합하다.
2. 반대로, 일반 함수는?
이건 독립된 상태를 여러 개 만들고 싶을 때 쓰는 방식이다.
function makeCounter() {
let num = 0;
return function () {
return ++num;
};
}
const c1 = makeCounter();
const c2 = makeCounter();
console.log(c1()); // 1
console.log(c1()); // 2
console.log(c2()); // 1 ← 완전히 독립됨
- 호출할 때마다 새로운 스코프(num)이 생김 → 각각 독립적인 상태
- 공유가 아니라 독립!!!!!
3. 언제 뭘 써야 하니? (정리)
| 목적 | 적합한 방식 | 예시 |
| 하나의 상태를 여러 호출에서 공유 | 즉시 실행 함수 | 카운터, 전역 설정, 한정된 리소스 |
| 여러 개의 독립된 상태를 각각 유지 | 함수 호출로 클로저 생성 | 사용자별 카운터, 여러 인스턴스 객체 |
캡슐화와 정보 은닉
캡슐화는 객체의 상태를 나타내는 프로퍼티와 프로퍼티를 참조하고 조작할 수 있는 동작인 메서드를 하나로 묶는 것을 말한다.
캡슐화는 객체의 특정 프로퍼티나 메서드를 감추기 위해 사용하기도 하는데 이를 정보 은닉이라고 한다.
정보 은닉은 외부에 공개할 필요가 없는 부분을 감춰 의도치 않는 접근으로 인해 객체의 상태가 변경되는 것을 막아 정보를 보호하고, 객체 간의 상호 의존성(=결합도)를 낮출 수 있다!!!
대부분의 객체 지향 프로그래밍 언어는 클래스를 정의하고 프로퍼티와 메서드에 대해 public, private, protected와 같은 접근 제한자를 선언해 공개 범위를 한정한다.
하 지 만!!!!!! 자바스크립트는 이 접근 제한자를 제공하지 않는다. (충격 ㄷㄷ)
따라서 자바스크립트의 객체의 모든 프로퍼티와 메서드는 기본적으로 public 하다...
function Person(name, age) {
this.name = name; // public
let _age = age; // private
// 인스턴스 메서드
this.sayHi = function () {
console.log( Hi! My name is ${this.name}. I am ${_age}. );
};
}
const me = new Person('Lee', 20);
me.sayHi(); // Hi! My name is Lee. I am 20.
console.log(me.name); // Lee
console.log(me._age); // undefined
const you = new Person('Kim', 30);
you.sayHi(); // Hi! My name is Kim. I am 30.
console.log(you.name); // Kim
console.log(you._age); // undefined
이 예제에서 name 프로퍼티는 public 하지만, _age 변수는 Person 생성자 함수의 지역 변수이므로 private 하다.
sayHi 메서드는 인스턴스 메서드이므로 Person 객체가 생성될 때마다 중복 생성된다.
이를 프로토타입 메서드로 변경해 중복 생성을 방지해 보자
잠깐!! 왜 방지해야 하죠? 🤔
인스턴스 메서드 vs 프로토타입 메서드 개념의 핵심을 짚고 넘어가자. (내가 잘 몰라서 정리하는 것입니다... 😅)
function Person(name, age) {
this.name = name; // public
let _age = age; // private
// 인스턴스 메서드
this.sayHi = function () {
console.log(`Hi! My name is ${this.name}. I am ${_age}.`);
};
}
// 만들어진 인스턴스들
const me = new Person('Lee', 20);
const you = new Person('Kim', 30);
new Person('Lee', 20) 실행 시
- 새로운 빈 객체 { } 생성
- 그 객체의 내부 슬롯 [[Prototype]]이 Person.prototype을 가리키게 함
- Person 함수 실행
- this가 새로 만든 객체를 참조
- this.name = 'Lee'
- let _age = 20
- this.sayHi = function() {...} 실행 → 새로운 함수 객체 생성 → sayHi 프로퍼티로 추가
결과적으로 만들어지는 것은!!!! 아래.
me = {
name: 'Lee',
sayHi: <함수 객체 #1>
}
이제 다음 줄에서 const you = new Person('Kim', 30); 실행하면
you = {
name: 'Kim',
sayHi: <함수 객체 #2>
}
즉, sayHi 함수 객체가 두 번 만들어짐.
왜 문제인가?!!?!!!!
자바스크립트에서 함수도 객체이다. 따라서 생성 비용이 있고, 메모리를 차지한다.
sayHi가 모든 인스턴스에서 동일한 동작을 하는데 각 인스턴스마다 새로 함수 객체를 생성하면 중복이다!!!!.
me.sayHi와 you.sayHi는 완전히 다른 함수 객체이지만 내부 동작은 똑같으니 메모리 낭비가 아닐 수 없다.
그래서 공유 가능한 메서드는 하나만 만들어서 모든 인스턴스가 공유하도록 하는 게 효율적이고 그 역할을 하는 게 바로 프로토타입(prototype)이다.
const Person = (function () {
let _age = 0; // private
// 생성자 함수
function Person(name, age) {
this.name = name; // public
_age = age;
}
// 프로토타입 메서드
Person.prototype.sayHi = function () {
console.log(`Hi My name is ${this name}. I am ${_age}` );
};
// 생성자 함수를 반환
return Person;
}());
const me = new Person( 'Lee', 20);
me.sayHi(); // Hi! My name is Lee. I am 20.
console.log(me.name); // Lee
console.log(me._age); // undefined
const you = new Person('Kim', 30);
you.sayHi(); // Hi! My name is Kim. I am 30.
console.log(you.name); // Kim
console.log(you._age); // undefined
위 패턴을 사용하면 접근 제한자를 제공하지 않는 자바스크립트에서도 정보 은닉이 가능한 것처럼 보인다...!!
이때, Person 생성자 함수와 sayHi 메서드는 이미 소멸한 즉시 실행 함수의 지역 변수 _age를 참조할 수 있는 클로저이다!!
그렇기에 발생하는 문제점이 하나 있다.
const me = new Person('Lee', 20);
me.sayHi(); // Hi! My name is Lee. I am 20.
const you = new Person('Kim', 30);
you.sayHi(); // Hi! My name is Kim. I am 30.
// _age 변수 값이 변경된다!!!!!
me.sayHi(); // Hi! My name is Lee. I am 30.
(내 나이 값이 바뀌었다... 나 20인데!!)
Person.prototype.sayHi 메서드는 즉시 실행 함수가 호출될 때 생성되고 Person.prototype.sayHi 메서드는 상위 스코프인 즉시 실행 함수의 렉시컬 환경의 참조를 내부 슬롯에 저장한다.
따라서 Person 생성자 함수의 모든 인스턴스가 상속을 통해 호출할 수 있는 Person.prototype.sayHi 메서드의 상위 스코프는 어떤 인스턴스로 호출하더라도 하나의 동일한 상위 스코프를 사용하게 된다.
즉, 프로토타입 메서드가 클로저를 사용해 즉시 실행 함수의 변수를 기억하면, 그 변수는 모든 인스턴스가 공유하게 되어서
Kim 인스턴스를 만든 게 Lee 인스턴스의 내부 상태까지 바꿔버린 것...
결론!!!!!!!
프로토타입 메서드는 모든 인스턴스가 공유하는 함수이기 때문에 그 함수가 상위 스코프(=즉시 실행 함수의 렉시컬 환경)에 있는 변수를 참조하면, 그 변수도 모두 공유하게 된다.
그래서 _age 같은 개별 인스턴스의 상태(state)를 클로저 안에 두면 안 되고... 자바스크립트는 정보 은닉을 100퍼센트 구현할 수 없다...!!!!!
자주 발생하는 실수
(클로저... 사실 개념을 이해했다고 해도 잘 활용하기는 엄청 어려울 것 같다... 그래서인지 책에서도 자주 발생하는 실수에 대해 언급하는 것인가!!!!! 😵)
var funcs = [];
for (var i = 0; i < 3; i++) {
funcs[i] = function () { return i; }; // 1
}
for (var j = 0; j < funcs.length; j++) {
console.log(funcs[j]()); // 2
}
첫 번째 for문의 코드 블록 내(1)에서 함수가 funcs 배열의 요소로 추가되고
두 번째 코드 블록 내(2)에서 funcs 배열의 요소로 추가된 함수를 순차적으로 호출한다.
이때 기대되는 반환 값은 0, 1, 2이겠지만, 아니다.
for문의 변수 선언문에서 var 키워드로 선언한 i 변수는 함수 레벨 스코프를 갖기 때문에 전역 변수이다.
전역 변수 i에는 순차적으로 0, 1, 2가 할당되고 funcs 배열의 요소로 추가된 함수를 호출하면 전역 변수 i를 참조해 i 값 3이 출력된다.
클로저를 이용해 위 예제를 바르게 동작하는 코드로 만들어보자.
var funcs = [];
for(var i = 0; i < 3; i++){
funcs[i] = (function (id){
return function(){
return id;
};
}(i));
}
for(var j = 0; j < funcs.length; j++){
console.log(func[j]());
}
1에서 즉시 실행 함수는 전역 변수 i에 현재 할당되어 있는 값을 전달 받아 매개변수 id에 할당한 후 중첩 함수를 반환하고 종료된다.
즉시 실행 함수가 반환한 함수는 funcs 배열에 순차적으로 저장된다.
이때 즉시 실행 함수의 매개변수 id는 즉시 실행 함수가 반환한 중첩 함수의 상위 스코프에 존재한다.
즉시 실행 함수가 반환한 중첩 함수는 클로저이고, 매개변수 id는 즉시 실행 함수가 반환한 중첩 함수에 묶여있는 자유 변수가 되어 값이 유지된다.
위 예제는 자바스크립트의 함수 레벨 스코프 특성으로 인해 for 문의 변수 선언문에서 var 키워드로 선언한 변수가 전역 변수가 되기 때문에 발생하는 현상이다. 사실 ES6의 let 키워드를 사용하면 이 같은 번거로움이 깔끔하게 해결된다!!!!!!!!
const funcs = [];
for (let i = 0; i < 3; i++) {
funcs[i] = function () { return i; };
}
for (let i = 0; i < funcs.length; i++) {
console.log(funcs[i]()); // 0 1 2
}
for 문의 변수 선언문에서 let 키워드로 선언한 변수를 사용하면 for 문의 코드 블록이 반복 실행될 때마다 for 문 코드 블록의 새로운 렉시컬 환경이 생성된다.
만약 for 문의 코드 블록 내에서 정의한 함수가 있다면 이 함수의 상위 스코프는 for 문의 코드 블록이 반복 실행될 때마다 생성된 for 문 코드 블록의 새로운 렉시컬 환경이다.
이때 함수의 상위 스코프는 for 문의 코드 블록이 반복 실행될 때마다 식별자(for 문의 변수 선언문에서 선언한 초기화 변수 및 for 문의 코드 블록 내에서 선언한 지역 변수 등)의 값을 유지하기 위해 for 문이 반복될 때마다 독립적인 렉시컬 환경을 생성하는 것이다!!!

그림의 1번을 보자. for 문의 변수 선언문에서 let 키워드로 선언한 초기화 변수를 사용한 for 문이 평가되면 먼저 새로운 렉시컬 환경(Loop Lexical Environment)을 생성하고 초기화 변수 식별자와 값을 등록한다.
그리고 새롭게 생성된 렉시컬 환경을 현재 실행 중인 실행 컨텍스트의 렉시컬 환경으로 교체한다.
그림의 2,3,4번을 보자. for 문의 코드 블록이 반복 실행되기 시작되면 새로운 렉시컬 환경(PER-ITERATION Lexical Environment)을 생성하고 for 문 코드 블록 내의 식별자와 값(증감문 반영 이전)을 등록한다.
그리고 새롭게 생성된 렉시컬 환경을 현재 실행 중인 실행 컨텍스트의 렉시컬 환경으로 교체한다.
그림의 5번을 보자. for 문의 코드 블록의 반복 실행이 모두 종료되면 for 문이 실행되기 이전의 렉시컬 환경을 실행 중인 실행 컨텍스트의 렉시컬 환경으로 되돌린다.
이처럼 let이나 const 키워드를 사용하는 반복문(for 문, for... in 문, for... of 문, while 문 등)은 코드 블록을 반복 실행할 때마다 새로운 렉시컬 환경을 생성하여 반복할 당시의 상태를 마치 스냅샷을 찍는 것처럼 저장한다.
단, 이는 반복문의 코드 블록 내부에서 함수를 정의할 때 의미가 있다.
반복문의 코드 블록 내부에 함수 정의가 없는 반복문이 생성하는 새로운 렉시컬 환경은 반복 직후, 아무도 참조하지 않기 때문에 가비
지 컬렉션의 대상이 된다!!!!!!!!!
모던 자바스크립트 딥 다이브를 공부하고 정리한 글입니다!
'JS' 카테고리의 다른 글
| JavaScript의 Event (1) (0) | 2025.12.01 |
|---|---|
| DOM과 브라우저 (0) | 2025.11.18 |
| 클로저(Closure)란? (1) (0) | 2025.11.11 |
| this에 대해서 (0) | 2025.11.11 |
| 실행 컨텍스트 보충 (전역 객체와 전역 코드 평가) (0) | 2025.11.09 |