이 글에서 정리하는 내용
스코프, 렉시컬, 실행 컨텍스트, 호이스팅, 클로저는 자바스크립트를 공부할 때 거의 한 번에 묶여서 나오는 개념입니다.
처음에는 전부 다른 단어처럼 보이지만, 실제로는 자바스크립트가 변수를 어디서 찾는지 정하고, 실행 전에 무엇을 먼저 준비하고, 함수가 왜 바깥 변수를 기억하는지를 설명하는 같은 흐름 안의 개념입니다.
이 글은 초심자가 읽고도 직접 설명할 수 있도록, 개념 정의보다 실제로 코드가 실행될 때 무슨 일이 일어나는지를 기준으로 아주 쉬운 순서로 정리합니다.
- 왜 이 개념들이 항상 같이 나오나
- 스코프는 변수의 활동 범위
- 렉시컬은 선언한 위치가 기준
- 실행 컨텍스트는 실행용 작업판
- 호이스팅은 선언을 먼저 준비하는 과정
- 클로저는 바깥 변수를 기억하는 함수
- 한 코드로 전체 흐름 연결하기
- 설명 연습용 정리
- 많이 받는 질문
왜 이 개념들이 항상 같이 나오나
![[JavaScript] 스코프, 렉시컬, 실행 컨텍스트, 호이스팅, 클로저 쉽게 이해하기 1 ChatGPT Image 2026년 2월 10일 오전 10 53 51 1](https://blogflow.kr/wp-content/uploads/2026/02/ChatGPT-Image-2026년-2월-10일-오전-10_53_51-1-1024x683.png)
이 다섯 개념은 서로 떨어진 지식이 아닙니다. 자바스크립트가 코드를 실행하는 순서를 따라가면 자연스럽게 한 줄로 연결됩니다.
| 순서 | 무슨 일이 일어나는가 |
|---|---|
| 1 | 변수를 어디서 찾을지 범위를 정한다 → 스코프, 렉시컬 |
| 2 | 실행 전에 선언을 먼저 준비한다 → 실행 컨텍스트, 호이스팅 |
| 3 | 함수가 바깥 변수를 계속 사용한다 → 클로저 |
즉, 스코프와 렉시컬은 변수 찾기 규칙이고, 실행 컨텍스트와 호이스팅은 실행 준비 과정이며, 클로저는 그 규칙 위에서 생기는 결과입니다.
처음 공부할 때는 용어를 각각 외우려고 해서 더 어렵습니다. 하지만 자바스크립트가 코드를 실행하는 흐름을 하나로 이해하면 훨씬 쉬워집니다.
처음부터 딱 이렇게 기억하면 됩니다
자바스크립트는 먼저 변수의 범위를 정하고, 실행 전에 선언을 준비하고, 실행하면서 필요한 값을 찾습니다. 이때 함수가 바깥 값을 계속 참조하면 클로저가 됩니다.
이 섹션을 보고 바로 말한다면
이 개념들은 따로 외우는 지식이 아니라, 자바스크립트가 코드를 읽고 실행하는 한 흐름을 나눠서 부르는 이름이라고 설명하시면 됩니다.
스코프는 변수의 활동 범위
스코프는 변수를 사용할 수 있는 범위입니다. 쉽게 말해 변수마다 활동할 수 있는 구역이 있다고 생각하시면 됩니다. 어떤 변수는 어디서든 보이고, 어떤 변수는 특정 함수 안에서만 보이고, 어떤 변수는 중괄호 안에서만 보입니다.
const siteName = 'My Blog';
function printInfo() {
const message = '환영합니다';
if (true) {
const today = '금요일';
console.log(siteName);
console.log(message);
console.log(today);
}
console.log(siteName);
console.log(message);
// console.log(today);
}
printInfo();
1. 전역 스코프
가장 바깥에서 선언한 변수의 범위입니다. 위 코드의 siteName이 여기에 해당합니다. 전역 스코프 값은 여러 곳에서 접근할 수 있어서 편하지만, 너무 많아지면 어디서 바뀌었는지 추적하기 어려워집니다.
2. 함수 스코프
함수 안에서 선언한 변수의 범위입니다. message는 printInfo 함수 안에서만 사용할 수 있습니다. 함수 밖으로 나가면 이 변수는 존재하지 않는 것처럼 보입니다.
3. 블록 스코프
중괄호 { } 안에서만 유효한 범위입니다. today는 if 블록 안에서만 사용할 수 있습니다. let과 const는 블록 스코프를 따르기 때문에 값이 바깥으로 새는 일을 줄여 줍니다.
변수는 어떻게 찾아갈까
자바스크립트는 변수를 찾을 때 현재 위치에서 먼저 찾습니다. 없으면 바로 바깥 스코프로 올라갑니다. 그래도 없으면 또 그 바깥으로 올라갑니다. 이렇게 끝까지 찾았는데 없으면 에러가 납니다.
const a = '전역';
function outer() {
const b = '바깥 함수';
function inner() {
const c = '안쪽 함수';
console.log(a);
console.log(b);
console.log(c);
}
inner();
}
outer();
위 코드에서 inner는 c를 현재 위치에서 찾고, b는 한 단계 바깥에서 찾고, a는 더 바깥 전역에서 찾습니다. 이것이 스코프 체인입니다.
var가 헷갈리는 이유
var는 블록 스코프가 아니라 함수 스코프를 따릅니다. 그래서 if나 for 안에서 선언해도 블록 밖에서 살아 있는 것처럼 보여 초심자가 많이 헷갈립니다.
if (true) {
var oldValue = 'var 변수';
let newValue = 'let 변수';
}
console.log(oldValue);
// console.log(newValue);
이 코드에서 oldValue는 출력되지만, newValue는 출력할 수 없습니다. 그래서 요즘은 일반적으로 let과 const 중심으로 코드를 작성합니다.
이 섹션을 보고 바로 말한다면
스코프는 변수를 사용할 수 있는 범위이고, 자바스크립트는 현재 범위에서 시작해 바깥쪽으로 올라가며 변수를 찾는다고 설명하시면 됩니다.
렉시컬은 선언한 위치가 기준
렉시컬은 말이 어렵지만 핵심은 하나입니다. 변수는 어디서 호출했는지가 아니라 어디서 선언했는지를 기준으로 찾는다는 것입니다. 이 규칙을 렉시컬 스코프라고 부릅니다.
const value = 100;
function printValue() {
console.log(value);
}
function run() {
const value = 200;
printValue();
}
run();
많은 초심자가 이 코드의 결과를 200으로 예상합니다. 왜냐하면 printValue()가 run 안에서 실행되기 때문입니다. 하지만 실제 결과는 100입니다.
왜 100이 나올까
printValue는 전역에서 선언되었습니다. 따라서 printValue는 자신이 실행되는 장소가 아니라, 자신이 만들어진 위치를 기준으로 바깥 변수를 찾습니다. 그래서 전역의 value를 보게 됩니다.
호출 위치가 아니라 선언 위치
이 부분이 중요합니다. run이 printValue를 호출했다고 해서, printValue가 갑자기 run 안에 있는 변수들을 자기 것처럼 사용하는 것이 아닙니다. 함수는 만들어질 때 이미 어느 바깥 스코프를 참고할지 결정됩니다.
왜 이 규칙이 필요할까
만약 호출한 위치에 따라 함수가 보는 변수가 계속 바뀐다면, 같은 함수라도 어디서 호출하느냐에 따라 동작이 계속 달라집니다. 그러면 코드를 예측하기 매우 어려워집니다. 렉시컬 규칙은 이런 혼란을 막아 줍니다.
this와 왜 자주 섞일까
렉시컬 스코프는 선언 위치가 기준입니다. 반면 this는 보통 호출 방식이 기준입니다. 그래서 둘을 같은 것으로 생각하면 헷갈리기 쉽습니다. 변수 찾기 규칙과 this 규칙은 따로 공부하는 것이 좋습니다.
이 섹션을 보고 바로 말한다면
렉시컬 스코프는 함수가 어디서 실행되었는지가 아니라, 어디서 선언되었는지를 기준으로 바깥 변수를 찾는 규칙이라고 설명하시면 됩니다.
실행 컨텍스트는 실행용 작업판
![[JavaScript] 스코프, 렉시컬, 실행 컨텍스트, 호이스팅, 클로저 쉽게 이해하기 2 ChatGPT Image 2026년 2월 10일 오전 10 57 46](https://blogflow.kr/wp-content/uploads/2026/02/ChatGPT-Image-2026년-2월-10일-오전-10_57_46-1024x683.png)
실행 컨텍스트는 자바스크립트가 코드를 실행할 때 만드는 작업판입니다. 초심자 기준으로는 지금 어떤 코드가 실행 중인지, 어떤 변수가 준비되었는지, 어디 바깥 스코프를 봐야 하는지 적어 두는 메모판이라고 이해하시면 됩니다.
전역 실행 컨텍스트와 함수 실행 컨텍스트
const x = 1;
function foo() {
const y = 2;
console.log(x + y);
}
foo();
프로그램이 시작되면 가장 먼저 전역 실행 컨텍스트가 만들어집니다. 그리고 foo()를 호출하는 순간 foo를 위한 실행 컨텍스트가 새로 만들어집니다. 즉, 함수가 호출될 때마다 그 함수만의 작업판이 하나 더 생긴다고 이해하시면 됩니다.
콜 스택과 함께 이해하기
이 작업판들은 콜 스택이라는 구조에 쌓입니다. 나중에 들어온 것이 먼저 처리되는 방식입니다. 그래서 함수가 호출되면 새 실행 컨텍스트가 스택 위에 올라가고, 실행이 끝나면 빠집니다.
function first() {
second();
}
function second() {
third();
}
function third() {
console.log('실행');
}
first();
이 코드에서는 전역 실행 컨텍스트 위에 first, 그 위에 second, 그 위에 third가 차례대로 쌓입니다. third가 끝나면 빠지고, 다시 second로 돌아가고, 그다음 first로 돌아갑니다.
실행 컨텍스트는 두 단계로 보면 쉽다
초심자는 실행 컨텍스트를 두 단계로 기억하면 편합니다. 첫 번째는 준비 단계이고, 두 번째는 실행 단계입니다.
준비 단계에서는 선언된 변수와 함수가 무엇인지 확인하고, 현재 스코프와 바깥 스코프 연결을 잡습니다. 실행 단계에서는 실제 코드가 한 줄씩 실행됩니다. 호이스팅은 이 준비 단계 때문에 보이는 현상입니다.
왜 실행 컨텍스트가 중요한가
실행 컨텍스트를 이해하면, 선언 전에 왜 함수가 호출되는지, 변수는 왜 어떤 때는 undefined가 나오고 어떤 때는 에러가 나는지, 함수 호출 순서에 따라 왜 스택이 쌓이는지 한 번에 연결해서 이해할 수 있습니다.
조금 더 들어가면 나오는 용어
공식 문서나 사양에서는 Lexical Environment, Variable Environment 같은 표현이 나옵니다. 초심자 단계에서는 이를 엄격하게 구분하기보다, 실행 컨텍스트 안에서 변수 정보와 바깥 스코프 연결을 관리하는 내부 구조라고 이해하면 충분합니다.
이 섹션을 보고 바로 말한다면
실행 컨텍스트는 자바스크립트가 코드를 실행할 때 만드는 작업판이고, 함수가 호출될 때마다 새로 만들어져 콜 스택에 쌓인다고 설명하시면 됩니다.
호이스팅은 선언을 먼저 준비하는 과정
호이스팅은 선언문이 코드 맨 위로 진짜 이동하는 것이 아닙니다. 자바스크립트가 코드를 실행하기 전에 선언부터 먼저 확인하고 준비하기 때문에, 마치 위로 끌어올린 것처럼 보이는 현상입니다.
console.log(a);
var a = 10;
// console.log(b);
let b = 20;
// console.log(c);
const c = 30;
var는 왜 undefined가 나올까
var a는 준비 단계에서 먼저 등록됩니다. 그래서 선언 전에 읽어도 아예 모르는 이름은 아니고, 아직 값이 들어가기 전 상태인 undefined가 나옵니다.
let과 const는 왜 에러가 날까
let과 const도 선언 자체는 미리 파악됩니다. 하지만 초기화되기 전까지는 접근하면 안 되도록 막혀 있습니다. 그래서 선언 전에 읽으면 에러가 납니다.
TDZ는 무엇인가
TDZ는 let과 const가 선언되었지만 아직 실제 사용이 허용되지 않는 구간입니다. 쉽게 말해 변수는 자리를 잡았지만 아직 문이 열리지 않은 상태입니다.
const x = 1;
{
// console.log(x);
const x = 2;
}
이 코드에서 블록 안의 x는 이미 존재할 예정인 변수입니다. 그래서 바깥 x를 보는 것도 허용되지 않고, 블록 안의 x가 초기화되기 전이라 에러가 발생합니다.
함수 선언문도 호이스팅된다
초심자가 자주 놓치는 부분인데, 함수 선언문은 변수와는 또 다르게 동작합니다. 함수 선언문은 선언 전에 호출할 수 있습니다.
sayHello();
function sayHello() {
console.log('안녕하세요');
}
하지만 함수 표현식은 다릅니다. 아래처럼 변수에 함수를 넣는 방식은 변수 규칙을 따르기 때문에 선언 전에 호출할 수 없습니다.
// sayHi();
const sayHi = function () {
console.log('안녕하세요');
};
한 번에 정리
호이스팅은 선언을 먼저 준비하는 과정입니다. 다만 var는 undefined로 보이고, let과 const는 TDZ 때문에 막히고, 함수 선언문은 선언 전에 호출할 수 있다는 차이가 있습니다.
이 섹션을 보고 바로 말한다면
호이스팅은 코드가 실제로 위로 올라가는 것이 아니라, 실행 전에 선언을 먼저 준비하는 과정이며 선언 종류에 따라 보이는 결과가 다르다고 설명하시면 됩니다.
클로저는 바깥 변수를 기억하는 함수
클로저는 처음에 가장 많이 막히는 개념입니다. 그런데 한 문장으로 줄이면 생각보다 단순합니다. 함수가 자신이 만들어질 때 주변에 있던 변수를 기억하는 것입니다.
function createCounter() {
let count = 0;
return function () {
count += 1;
return count;
};
}
const counter = createCounter();
console.log(counter());
console.log(counter());
console.log(counter());
위 코드에서 createCounter() 실행은 끝났지만, 반환된 함수는 여전히 count를 기억합니다. 그래서 호출할 때마다 1, 2, 3으로 증가합니다.
왜 count가 사라지지 않을까
보통 함수 실행이 끝나면 그 안의 지역 변수도 끝난다고 생각하기 쉽습니다. 하지만 여기서는 반환된 함수가 아직 count를 계속 사용하고 있습니다. 자바스크립트 엔진은 아직 필요한 값이라고 판단하기 때문에 바로 버리지 않습니다.
클로저는 왜 필요한가
클로저를 사용하면 값을 숨기면서도 필요한 방식으로만 조작할 수 있습니다. 전역 변수로 노출하지 않고도 특정 상태를 계속 기억하게 만들 수 있기 때문입니다.
function createToggle() {
let isOpen = false;
return function () {
isOpen = !isOpen;
return isOpen;
};
}
const toggle = createToggle();
console.log(toggle());
console.log(toggle());
이 예제에서는 토글 상태를 함수 바깥으로 직접 노출하지 않으면서도 내부에서 계속 기억할 수 있습니다. 이런 패턴은 카운터, 토글, 캐시, 이벤트 핸들러, 비동기 콜백 등에서 자주 사용됩니다.
렉시컬과 클로저의 관계
클로저는 갑자기 따로 생기는 마법 같은 기능이 아닙니다. 렉시컬 스코프 규칙 때문에 함수가 자신이 선언된 위치의 바깥 변수를 참조할 수 있고, 그 참조가 계속 유지되면 클로저처럼 동작하는 것입니다.
초심자가 자주 하는 착각
클로저는 내부 함수만 있으면 무조건 특별한 기술이라고 생각하기 쉽습니다. 하지만 내부 함수가 바깥 변수를 참조하는 것은 자바스크립트의 기본 동작입니다. 그 동작을 실제 기능으로 활용할 때 클로저를 쓴다고 이해하는 편이 더 정확합니다.
이 섹션을 보고 바로 말한다면
클로저는 함수가 자신이 만들어질 때 주변에 있던 변수를 기억해서, 나중에도 계속 사용할 수 있는 동작이라고 설명하시면 됩니다.
한 코드로 전체 흐름 연결하기
이제 다섯 개념을 한 코드 안에서 같이 보겠습니다. 아래 예제는 조금 길지만, 지금까지 배운 내용을 한 번에 연결하기 좋습니다.
const appName = 'Study App';
function makeTimer() {
let time = 0;
function start() {
console.log(appName);
console.log(time);
time += 1;
return time;
}
return start;
}
const timer = makeTimer();
console.log(timer());
console.log(timer());
1. 스코프
appName은 전역 스코프에 있고, time은 makeTimer 함수 스코프에 있고, start는 그 안에서 선언된 내부 함수입니다. 따라서 start는 자신의 범위 안에서 값을 찾다가 없으면 바깥으로 올라가 time과 appName을 찾습니다.
2. 렉시컬
start는 makeTimer 안에서 선언되었습니다. 그래서 나중에 어디서 호출되든, 자신이 참조해야 할 바깥 범위는 makeTimer 기준으로 고정됩니다.
3. 실행 컨텍스트
프로그램 시작 시 전역 실행 컨텍스트가 생기고, makeTimer() 호출 시 그 함수의 실행 컨텍스트가 생기고, 이후 timer()를 호출할 때마다 start의 실행 컨텍스트가 새로 생깁니다.
4. 호이스팅
각 실행 컨텍스트가 만들어질 때는 실행 전에 선언 정보가 먼저 준비됩니다. 그래서 함수와 변수에 대한 내부 준비가 끝난 뒤 실제 코드가 실행됩니다.
5. 클로저
makeTimer() 실행이 끝난 뒤에도 반환된 start 함수는 time을 계속 기억합니다. 그래서 첫 호출에는 1, 두 번째 호출에는 2가 됩니다. 이것이 클로저입니다.
한 문장으로 전체 정리
자바스크립트는 선언 위치를 기준으로 변수 범위를 정하고, 실행 전에 선언을 준비한 뒤, 실행 중에 필요한 값을 스코프 체인으로 찾습니다. 그리고 함수가 바깥 값을 계속 참조하면 클로저가 됩니다.
설명 연습용 정리
여기부터는 읽는 용도가 아니라, 직접 설명하는 연습을 위한 요약입니다. 면접이나 공부 복습 때 아래 흐름대로 말하면 비교적 깔끔하게 설명할 수 있습니다.
30초 설명 버전
스코프는 변수의 범위이고, 렉시컬 스코프는 변수를 호출 위치가 아니라 선언 위치 기준으로 찾는 규칙입니다. 실행 컨텍스트는 코드를 실행할 때 만들어지는 작업판이고, 호이스팅은 그 작업판이 만들어질 때 선언을 먼저 준비하는 과정입니다. 클로저는 함수가 바깥 변수를 기억해 계속 사용하는 동작입니다.
1분 설명 버전
자바스크립트는 먼저 함수와 변수의 선언 위치를 기준으로 스코프 구조를 정합니다. 이것이 렉시컬 스코프입니다. 그리고 코드를 실행할 때 실행 컨텍스트를 만들고, 그 안에서 변수와 함수 선언을 먼저 준비합니다. 이 준비 때문에 호이스팅이 보입니다. 이후 실제 실행 단계에서는 현재 스코프에서 변수를 찾고, 없으면 바깥 스코프로 올라가며 찾습니다. 이 과정에서 함수가 바깥 변수를 계속 참조하면 클로저가 됩니다.
초심자가 자주 틀리는 포인트
| 헷갈리는 부분 | 정리 |
|---|---|
| 호이스팅은 코드가 진짜 위로 올라간다 | 실제 이동이 아니라 실행 전에 선언을 준비하는 현상입니다 |
| 함수는 호출한 위치 기준으로 변수를 찾는다 | 호출 위치가 아니라 선언 위치 기준입니다 |
| 클로저는 특별한 문법이다 | 별도 문법이 아니라 함수가 바깥 변수를 기억하는 기본 동작입니다 |
마지막 한 줄 요약
스코프는 범위, 렉시컬은 기준, 실행 컨텍스트는 작업판, 호이스팅은 준비 과정, 클로저는 기억 효과라고 정리하면 전체 구조가 머릿속에 오래 남습니다.
많이 받는 질문
Q. 스코프와 렉시컬 스코프는 같은 말인가요?
완전히 같은 말은 아닙니다. 스코프는 변수의 범위 자체를 말하고, 렉시컬 스코프는 그 범위를 선언 위치 기준으로 결정하는 규칙을 말합니다.
Q. let과 const도 호이스팅되나요?
네, 선언 자체는 실행 전에 파악됩니다. 다만 초기화되기 전에는 접근이 막혀 있어서 var처럼 보이지 않을 뿐입니다.
Q. 클로저는 언제 쓰나요?
카운터, 토글, 캐시, 이벤트 핸들러처럼 어떤 값을 계속 기억해야 하는 기능을 만들 때 자주 사용합니다.
Q. 실행 컨텍스트를 가장 쉽게 이해하는 방법은 무엇인가요?
자바스크립트가 코드를 실행할 때 잠깐 만드는 작업판이라고 이해하시면 됩니다. 현재 실행 중인 코드와 변수 정보, 바깥 스코프 연결이 여기에서 관리됩니다.
Q. 함수 선언문과 함수 표현식이 호이스팅에서 왜 다르게 보이나요?
함수 선언문은 선언 전에 호출할 수 있지만, 함수 표현식은 변수 규칙을 따르기 때문에 선언 전에 호출할 수 없습니다.
Q. 이 다섯 개념을 따로 외워야 하나요?
따로 외우기보다 자바스크립트가 범위를 정하고, 선언을 준비하고, 실행하면서 값을 찾고, 필요하면 바깥 값을 기억하는 흐름으로 함께 이해하는 것이 더 쉽습니다.