클로저는 함수와 그 함수가 선언될 당시의 렉시컬 환경(Lexical Environment)에 대한 참조를 함께 저장하는 구조이다.
내부 함수가 실행될 때, 이 참조를 통해 자신이 선언될 당시 주변 스코프의 변수들에 접근할 수 있으며,
외부 함수의 실행이 완료된 후에도 해당 변수들이 메모리에 남아있어 접근이 가능하다.
렉시컬 환경
"함수나 블록이 선언될 때, 그 위치에서 접근 가능한 변수들이 담긴 객체"
✅ 렉시컬 환경의 구성
1️⃣ 환경 레코드 (Environment Record)
- 현재 스코프(블록/함수 등)에서 선언된 변수(예: var, let, const)를 저장하는 객체.
2️⃣ 외부 렉시컬 환경에 대한 참조 (Outer Lexical Environment Reference)
- 상위(외부) 스코프의 렉시컬 환경을 가리키는 참조.
클로저의 개념을 잘못 사용한 예시
함수 안에서 선언한 변수는 함수 바깥에서 접근할 수 없다.
function inner() {
console.log(count)
}
function outer() {
let count = 0;
inner()
}
inner(); // ReferenceError: count is not defined
클로저 예시 1
n0 = 'n0';
var v0 = 'v0';
let l0 = 'l0';
const c0 = 'c0';
console.log(v0, n0, l0, c0); // v0 n0 l0 c0
console.log(window.v0, window.n0, window.l0, window.c0); // v0 n0 undefined undefined
function fn1() {
n1 = 'n1';
var v1 = 'v1';
let l1 = 'l1';
const c1 = 'c1';
return function fn2() {
n2 = 'n2';
console.log(n0, n1, n2); // n0 n1 n2
var v2 = 'v2';
console.log(v0, v2); // v0 v2
console.log(v1); // v1
let l2 = 'l2';
console.log(l0, l2); // l0 l2
console.log(l1); // l1
const c2 = 'c2';
console.log(c0, c2); // c0 c2
console.log(c1); // c1
}
}
let f2 = fn1();
f2();
f2 = null;
1️⃣ fn1이 종료되어도, fn2가 fn1의 지역변수에 접근 가능 [ 클로저가 접근 가능한 변수들을 객체에 저장하기 때문에 ]
2️⃣ 이때, 그 “렉시컬 환경”은 heap 같은 공간에 살아있음
3️⃣ f2=null로 참조를 없애면 → 가비지 컬렉션으로 heap에서 제거됨
클로저 예시 2
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
makeAdder는 함수를 반환하는 함수다.
두 함수는 각자 독립적인 클로저로, 자신만의 x 값을 heap에 보관한다.
GC 관점에서는 add5, add10이 참조를 잃으면 heap에서 정리된다.
렉시컬 스코프
자바스크립트는 함수를 어디서 호출했는지가 아니라, 어디서 정의했는지를 기준으로 변수 접근 범위를 결정한다.
function outer() {
let x = 2;
function inner() {
console.log(x);
}
return inner;
}
const fn = outer();
fn(); // 2
이러한 개념을 렉시컬 스코프, 또는 정적 스코프라고 한다.
실행 중에 결정되는 게 아니라 코드 작성 시점 기준이다.
성능 관련 고려사항
모든 클로저가 항상 필요한 건 아니다.
무심코 내부에 함수를 계속 만들면, 그만큼 메모리도 차지하고 성능에도 영향을 준다.
예를 들어 생성자 함수 안에 메서드를 직접 만들면, 인스턴스가 생길 때마다 그 함수도 새로 만들어진다:
function MyObject(name, message) {
this.name = name;
this.message = message;
this.getName = function () {
return this.name;
};
this.getMessage = function () {
return this.message;
};
}
클로저가 필요 없다면 아래처럼 prototype을 쓰는 게 더 낫다:
function MyObject(name, message) {
this.name = name;
this.message = message;
}
MyObject.prototype.getName = function () {
return this.name;
};
MyObject.prototype.getMessage = function () {
return this.message;
};
이렇게 하면 함수는 한 번만 정의되고, 인스턴스들이 공유해서 쓴다. 메모리 낭비도 없고 성능도 낫다.
클로저를 유용하게 사용하는 방법
https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Closures#실용적인_클로저
참고자료
'FE > JS' 카테고리의 다른 글
JavaScript - this (0) | 2025.05.19 |
---|---|
자바스크립트 실행 컨텍스트 (0) | 2025.05.19 |
자바스크립트 호이스팅 정리 (1) | 2025.05.18 |
null, undefined, undeclared, NaN (1) | 2025.05.17 |
JavaScript: var, let, const (0) | 2025.05.13 |