자바스크립트를 깊이 있게 이해하기 위해서는 실행 컨텍스트, 스코프 체인, 클로저의 관계를 단계적으로 파악해야 합니다. 특히 프론트엔드 개발 면접에서는 이 세 가지 개념을 유기적으로 연결하여 설명할 수 있어야 고득점을 받을 수 있습니다. 본문에서는 실행 컨텍스트의 개념에서 시작해 스코프 체인이 어떻게 형성되는지를 살펴보고, 콜백 함수와 클로저의 연관성을 실제 예제로 설명하며, 마지막으로 면접에서 자주 등장하는 클로저 관련 비교 질문에 어떻게 답변해야 하는지도 함께 안내합니다.
실행 컨텍스트란 무엇인가?
실행 컨텍스트(Execution Context)는 자바스크립트 코드가 실행되는 환경 또는 문맥(Context)을 의미합니다. 자바스크립트는 싱글 스레드 기반 언어이며, 코드를 실행하기 위해 먼저 해당 코드를 평가하고 실행하는 두 단계를 거칩니다. 이때 코드가 실행되기 전에 관련 변수, 함수 선언, 스코프 정보 등이 저장되는 공간이 바로 실행 컨텍스트입니다. 실행 컨텍스트는 크게 전역 실행 컨텍스트, 함수 실행 컨텍스트, eval 실행 컨텍스트로 나눌 수 있으며, 각 컨텍스트는 변수 객체(Variable Object), 스코프 체인(Scope Chain), this 값 등을 포함합니다.
예를 들어, 함수가 호출되면 해당 함수의 실행 컨텍스트가 콜 스택(Call Stack)에 쌓이고, 실행이 끝나면 제거됩니다. 이렇게 컨텍스트가 스택 형태로 관리되기 때문에 함수 간의 실행 흐름, 변수 참조 순서 등을 파악할 수 있습니다. 실행 컨텍스트가 생성되는 과정은 다음과 같습니다:
- 변수, 함수 선언 저장 (호이스팅)
- this 바인딩 결정
- 스코프 체인 설정
이 개념을 이해하면 자바스크립트에서 변수가 언제, 어떻게 유효한지, 함수 내부에서 외부 변수에 어떻게 접근하는지를 명확히 이해할 수 있습니다.
스코프 체인의 동작 방식
스코프 체인(Scope Chain)은 현재 실행 중인 코드에서 변수에 접근하기 위한 참조 목록입니다. 자바스크립트는 렉시컬 스코핑(Lexical Scoping)을 채택하고 있기 때문에, 함수가 선언될 당시의 환경을 기준으로 스코프가 결정됩니다. 즉, 코드의 위치에 따라 참조할 수 있는 변수 범위가 결정된다는 뜻입니다.
스코프 체인은 현재 함수의 변수 객체와 외부 함수들의 변수 객체를 연결한 구조로 형성됩니다. 만약 현재 스코프에 원하는 변수가 없다면, 엔진은 외부 스코프로 이동하여 순차적으로 변수를 검색합니다. 이를 통해 중첩 함수에서 외부 변수에 접근할 수 있게 됩니다.
예를 들어 다음과 같은 코드에서:
function outer() {
const a = 10;
function inner() {
console.log(a);
}
inner();
}
inner
함수는 자신 내부에 변수 a
가 없지만, 외부 함수 outer
에 있는 변수를 참조할 수 있습니다. 이는 스코프 체인을 통해 실행 컨텍스트가 해당 변수를 검색해 찾았기 때문입니다. 이 동작은 런타임이 아니라 함수가 정의될 때 결정되며, 따라서 스코프 체인은 정적 구조입니다. 이러한 구조는 클로저가 만들어지는 기반이 됩니다.
콜백 함수와 클로저: 실전에서 만나는 구조
클로저는 콜백 함수와 함께 사용할 때 실무에서 자주 마주치는 개념입니다. 콜백 함수는 다른 함수의 인자로 전달되어 특정 시점에 실행되는 함수이며, 비동기 처리나 반복 이벤트 상황에서 자주 사용됩니다. 이 때, 콜백 내부에서 외부 변수에 접근하려면 클로저가 필요합니다.
가장 대표적인 예시는 setTimeout
과 for
루프를 함께 사용할 때 발생하는 문제입니다:
for (var i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // 3, 3, 3 출력됨
}, 1000);
}
위 코드는 i
의 최종 값 3이 캡처되어 3이 세 번 출력됩니다. 이는 클로저가 i
의 참조를 기억하기 때문입니다. 해결 방법은 즉시 실행 함수(IIFE) 또는 let
을 사용하는 것입니다.
for (let i = 0; i < 3; i++) {
setTimeout(function () {
console.log(i); // 0, 1, 2 출력
}, 1000);
}
또는,
for (var i = 0; i < 3; i++) {
(function(index) {
setTimeout(function () {
console.log(index);
}, 1000);
})(i);
}
이처럼 콜백 함수와 클로저는 **비동기 흐름에서 상태를 기억**하기 위한 강력한 수단입니다. 실제 면접에서는 이 예제에 대한 설명과 해결법을 물어보며, 클로저의 이해도와 실전 적용 능력을 함께 평가합니다.
클로저의 형성과 활용
클로저(Closure)는 함수가 선언될 당시의 외부 스코프를 기억하고, 그 환경에 계속 접근할 수 있는 자바스크립트의 기능입니다. 클로저는 실행 컨텍스트와 스코프 체인 개념을 기반으로 형성되며, 외부 함수가 종료된 이후에도 내부 함수가 외부 변수에 접근할 수 있도록 만듭니다.
예를 들어 아래와 같은 구조가 클로저의 전형적인 예입니다.
function createCounter() {
let count = 0;
return function () {
count++;
console.log(count);
};
}
const counter = createCounter();
counter(); // 1
counter(); // 2
createCounter
는 외부 함수이고, 반환된 내부 함수는 count
라는 지역 변수에 계속 접근할 수 있습니다. 이처럼 클로저는 상태 유지를 가능하게 하며, 특히 다음과 같은 상황에서 강력한 도구로 사용됩니다:
- 비공개 변수 구현 (정보 은닉)
- 이벤트 핸들러에서 변수 유지
- React 훅에서 이전 상태 캡처
하지만 클로저는 변수 참조를 유지하므로, 불필요한 참조가 남아 메모리 누수가 발생할 수 있습니다. 이를 방지하려면 참조 해제 또는 메모리 관리를 신경 써야 합니다.
면접에서 자주 등장하는 비교 질문과 대응법
프론트엔드 개발자 면접에서는 클로저 단독 질문 외에도 실행 컨텍스트, 스코프, 콜백 함수와 함께 비교하거나 연결해서 질문하는 사례가 많습니다. 이 질문들은 단순 지식이 아니라 개념 간 연결성과 실전 적용 능력을 테스트합니다.
대표 질문 예시는 다음과 같습니다:
- 클로저와 실행 컨텍스트는 어떻게 다르고 연결되나요?
- 콜백 함수에서 클로저가 어떤 역할을 하나요?
- React에서 클로저 때문에 발생할 수 있는 문제는?
이러한 질문에는 구조적으로 이렇게 답변할 수 있습니다:
"실행 컨텍스트는 코드 실행 환경을 의미하고, 스코프 체인은 변수 참조 구조이며, 클로저는 이 스코프 체인을 내부 함수가 기억하고 사용하는 구조입니다. 콜백 함수 내부에서 외부 변수를 참조하는 구조 역시 클로저의 대표 사례입니다."
특히 React에서는 useEffect
또는 setInterval
내부에서 클로저 문제가 자주 발생합니다. 대표적인 stale closure 문제를 예로 들 수 있습니다:
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 항상 0 출력됨
}, 1000);
return () => clearInterval(timer);
}, []);
이 문제를 해결하려면 prev
상태로 접근하거나 의존성 배열을 수정해야 합니다:
useEffect(() => {
const timer = setInterval(() => {
setCount(prev => {
console.log(prev + 1);
return prev + 1;
});
}, 1000);
return () => clearInterval(timer);
}, []);
이처럼 면접에서는 정의, 예제, 실전 사례, 비교 설명 순으로 구성된 답변이 가장 신뢰를 얻습니다.
자바스크립트의 실행 컨텍스트, 스코프 체인, 클로저는 단독 개념이 아닌 하나의 흐름 속에 존재하는 구조입니다. 여기에 콜백 함수와 React까지 결합하면 실전에서의 클로저 개념을 완전히 이해할 수 있습니다. 본문에서 소개한 개념적 흐름과 예제, 면접 질문 유형을 반복적으로 복습하고, 자신만의 설명 방식을 익혀두면 면접과 실무 모두에서 강점을 가질 수 있습니다.