클로저는 함수와 그 함수가 선언된 렉시컬 환경의 조합을 의미하는 중요한 자바스크립트 개념이다.
핵심 특징
- 클로저는 자바스크립트 고유의 개념이 아니며, 하스켈, 파스칼, 얼랭, 스칼라 등 다양한 프로그래밍 언어에서 사용되는 특성
- ECMAScript 사양에는 클로저에 대한 정의가 명시되어 있지 않으나, MDN에서 상세히 다룸
렉시컬 스코프
렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 정의했는지에 따라 상위 스코프가 결정되는 개념이다. 예제를 통해 살펴보자
const x = 1;
function outerFunc() {
const x = 10;
function innerFunc() {
console.log(x);
}
innerFunc();
}
outerFunc();
이 예제에서 innerFunc 함수는 outerFunc의 내부에서 정의되었으므로, outerFunc의 변수에 접근할 수 있다. 중첩 함수 innerFunc가 자신이 선언된 렉시컬 환경의 변수를 참조하면서 클로저가 형성된다.
클로저는 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하는 중요한 매커니즘을 제공하며, 이는 자바스크립트에서 private 변수를 구현하는 방법 중 하나로 활용된다.
24.1 렉시컬 스코프
렉시컬 스코프는 함수의 상위 스코프가 정의되는 방식을 설명한다.
핵심 개념
함수를 어디서 호출하는지가 아닌, 어디에서 정의했는지에 따라 상위 스코프가 결정
const x = 1;
function foo() {
const x = 10
bar()
}
function bar() {
consloe.log(x)
}
foo() // ?
bar() // ?
위 예제의 foo함수와 bar함수는 모두 전역에서 정의된 전역함수이다. 함수의 상위 스코프는 함수는 어디서 정의했느냐에 따라 결정되므로 foo, bar함수의 상위 스코프는 전역이다. 함수를 어디서 호출하는지는 함수의 상위 스코프 결정에 어떠한 영향도 주지 못한다.
스코프의 실체는 실행 컨텍스트의 렉시컬 환경이며, 이 렉시컬 환경은 자신의 `외부 렉시컬 환경에 대한 참조`를 통해 상위 렉시컬 환경과 연결된다. 이것이 바로 스코프 체인이다.
따라서 `함수의 상위 스코프를 경정한다`는 것은 `렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 저장할 참조값을 결정한다` 는 것과 같다.
정리 : 렉시컬 환경의 `외부 렉시컬 환경에 대한 참조`에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다. 이것이 바로 렉시컬 스코프다.
24.2 함수 객체의 내부 슬롯
함수가 정의된 환경과 호출되는 환경은 다를 수 있다. 따라서 렉시컬 스코프가 가능하려면 함수는 자신이 호출되는 환경과는 상관없이 자신이 정의된 환경, 즉 상위 스코프를 기억해야 한다. 이를 위해 함수는 자신의 내부 슬롯에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다.
자신의 내부 슬롯 [[Enviroment]] 에 저장된 상위 스코프의 참조는 현재 실행중인 실행 컨텍스트의 렉시컬 환경을 가리킨다
함수 객체의 내부슬롯 [[Enviroment]] 에 저장된 현재 실행중인 실행 컨텍스트의 렉시컬 환경의 참조가 바로 상위 스코프다. 또한 자신이 호출되었을 때 생성될 함수 렉시컬 환경의 "외부 렉시컬 환경에 대한 참조"에 저장된 참조값이다. 함수 객체는 내부 슬롯 [[Enviroment]] 에 저장한 렉시컬 환경의 참조, 즉 상위 스코프를 자신이 존재하는 한 기억한다.
const x = 1;
function foo() {
const x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // ?
bar(); // ?
위 예제의 전역 스코프
전역변수 `x = 1`, `foo`함수 정의, `bar`함수 정의
foo 함수의 스코프
지역변수 `x=10`, `bar`함수 호출
bar 함수가 호출될 때, 렉시컬 스코프의 원칙에 따라 전역 스코프의 x값인 1을 참조한다. 이는 bar 함수가 전역에서 정의되었기 때문이다. foo 함수 내부의 x = 10은 bar 함수에서 접근할 수 없으며, 이것이 바로 렉시컬 스코프의 중요한 특징을 보여주는 예시이다.
24.3 클로저와 렉시컬 환경
function createCounter() {
let count = 0;
return {
increase() {
return ++count;
},
decrease() {
return --count;
},
getCount() {
return count;
}
};
}
const counter = createCounter();
console.log(counter.increase()); // 1
console.log(counter.increase()); // 2
console.log(counter.decrease()); // 1
이 예제에서는 count 변수가 클로저에 의해 은닉되어있다. 외부에서는 직접 접근할 수 없고, 오직 제공된 메서드를 통해서만 조작이 가능하다.
클로저의 특징
- 내부 함수는 외부 함수의 변수에 접근할 수 있다.
- 외부 함수의 실행이 끝나도 내부 함수가 외부 함수의 변수를 참조할 수 있다.
- 데이터 프라이버시를 구현할 수 있다.
정리: 클로저는 캡슐화와 정보은닉을 구현하는데 매우 유용한 도구이다.
문제
문제 1
다음 중 클로저의 특징으로 올바르지 않은 것은?
- 내부 함수는 외부 함수의 변수에 접근할 수 있다
- 외부 함수의 실행이 끝나면 내부 함수는 외부 함수의 변수에 접근할 수 없다
- 데이터 프라이버시를 구현할 수 있다
- 함수가 선언된 렉시컬 환경을 기억한다
문제 2
다음 코드의 실행 결과는?
let x = 1;
function outer() {
let x = 10;
function inner() {
console.log(x);
}
return inner;
}
const innerFunc = outer();
x = 2;
innerFunc();
- 1
- 2
- 10
- undefined
문제 3
렉시컬 스코프에 대한 설명으로 옳은 것은?
- 함수의 상위 스코프는 함수가 호출된 위치에 따라 결정된다
- 함수의 상위 스코프는 함수가 정의된 위치에 따라 결정된다
- 함수의 상위 스코프는 실행 시점에 동적으로 결정된다
- 함수의 상위 스코프는 항상 전역 스코프이다
정답
1.
정답: 2번
설명: 클로저의 핵심 특징은 외부 함수의 실행이 종료된 후에도 내부 함수가 외부 함수의 변수를 계속 참조할 수 있다는 것이다.
2.
정답: 3번
설명: inner 함수는 자신이 선언된 렉시컬 환경(outer 함수의 스코프)을 기억하므로, outer 함수의 지역변수 x값인 10을 출력한다. 전역변수 x의 값이 2로 변경되어도 영향을 받지 않는다.
3.
정답: 2번
설명: 렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에서 정의했는지에 따라 상위 스코프가 결정되는 개념이다.
'FE > JavaScript' 카테고리의 다른 글
| [#0 Node] 기초개념 (3) | 2025.07.26 |
|---|---|
| [26장] 모던 자바스크립트 Deep Dive - ES6 함수의 추가 기능 (0) | 2025.01.12 |
| [21장] 모던 자바스크립트 Deep Dive - 빌트인 객체 (0) | 2024.12.11 |
| [17장] 모던 자바스크립트 Deep Dive - 생성자 함수에 의한 객체 생성 (1) | 2024.12.02 |
| [11장] 모던 자바스크립트 Deep Dive - 원시 값과 객체의 비교 (1) | 2024.11.11 |