이벤트 전파(Event Propagation)는 브라우저에서 이벤트가 발생했을 때, 이벤트가 DOM 트리를 따라 이동하는 방식을 의미합니다. 크게 캡쳐링(Capturing), 버블링(Bubbling) 두 단계가 있으며, 이벤트 위임(Event Delegation)은 이 특성을 활용하는 기법입니다.
키워드 설명
캡쳐링(Caturing Phase)
이벤트가 최상위 document 에서 시작해 목표 요소까지 내려오는 단계입니다.
기본적으로 JS에서 이벤트 리스너를 등록할 때 addEventListener 의 세 번째 인자를 true 로 설정하면 캡쳐링 단계에서 이벤트를 잡을 수 있습니다.
parent.addEventListener('click', () => {
console.log('캡쳐링 단계에서 부모 클릭!');
}, true);
버블링(Bubbling Phase)
이벤트가 목표 요소에서 발생한 후, 부모 요소로 거슬러 올라가는 단계입니다.
대부분의 이벤트는 기본적으로 버블링을 지원하며, 세 번째 인자를 생략하거나 false 로 설정하면 버블링 단계에서 이벤트를 잡습니다.
parent.addEventListener('click', () => {
console.log('버블링 단계에서 부모 클릭!');
});
이벤트 위임(Event Delegation)
자식 요소 각각에 이벤트를 등록하는 대신, 공통 부모 요소 하나에 이벤트를 등록하고 이벤트 버블링을 이용해 처리하는 기법입니다.
자바스크립트는 동적 타입 언어로, 변수의 타입이 실행 중에 변경될 수 있습니다. 원시 타입과 참조 타입이 있으며, 필요에 따라 JS 엔진이 타입을 자동으로 변환하는 타입 강제(Type Coercion)가 발생합니다. - 원시 타입: String, Number, BigInt, Boolean, undefined, null, Symbol - 참조 타입: Object, Array, Function 등 타입 변환은 암묵적(자동) 또는 명시적(개발자가 직접)으로 발생할 수 있으며, 비교 연산 시 주의가 필요합니다.
키워드 설명
암묵적 타입 변환(Implicit Coercion): JS 엔진이 자동으로 타입을 변환
"5" + 3 // "53
// -> +연산자는 덧셈과 문자열연결 2가지 역할을 함
// ->피연산자 중 하나가 문자열이면, JS는 다른 피연산자도 문자열로 자동 변환해서 연결
"5" - 3 // 2
// -> -연산자는 덧셈이 아닌 산술 연산
// -> JS는 산술 연산을 수행하기 위해 피연산자를 숫자형으로 자동 변환
// -, *, /, % 같은 산술 연산자는 항상 숫자형 연산을 수행하므로 문자열을 숫자로 변환
true + 1 // 2
실행 컨텍스트(Execution Context)는 자바스크립트 코드가 실행되기 위해 필요한 정보(변수, 스코프, this, 환경)를 모아둔 실행 환경 객체입니다. 함수나 전역 코드가 실행될 때마다 새로운 실행 컨텍스트가 생성되어 콜스택(Call Stack)에 쌓이고, 자바스크립트는 이 컨텍스트를 기반으로 변수 조회, 스코프 결정, this 바인딩 등을 수행합니다. 즉, 실행 컨텍스트는 “코드가 어떻게 동작할지 결정하는 모든 정보”를 담고 있는 구조입니다.
키워드 설명
실행 컨텍스트에 포함되는 핵심 요소
Lexical Environment (렉시컬 환경)
let/const 선언
환경 레코드(Environment Record)
스코프 체인 정보
Variable Environment (변수 환경)
var 선언 정보
초기 스코프 정보
This Binding
해당 컨텍스트에서 this가 무엇을 가리키는지 저장
실행 컨텍스트 종류
전역 실행 컨텍스트
자바스크립트 코드 최초 실행 시 생성
전역 스코프 & 전역 객체(window/global) 연결
함수 실행 컨텍스트
함수가 호출될 때마다 생성
함수 내부 변수/매개변수/스코프 정보 저장
실행 컨텍스트 동작 과정 (필수 2단계)
생성 단계 (Creation Phase)
환경 구성 (렉시컬/변수 환경 생성)
변수 및 함수 선언을 메모리에 등록
호이스팅 발생
var → undefined로 초기화
let/const → TDZ에 들어감
this 바인딩 결정
실행 단계 (Execution Phase)
코드가 실제 실행됨
변수에 값 할당, 함수 호출 등 수행됨
코드 예시
예시 1 - 전역 + 함수 실행 컨텍스트
var x = 1;
function foo() {
var y = 2;
console.log(x + y);
}
foo();
/*
1. 전역 실행 컨텍스트 실행
- x, foo가 메모리에 등록됨(호이스팅)
2. foo()호출 -> 함수 실행 컨텍스트 생성
- y 등록
3. foo 컨텍스트 종료 -> 스택에서 제거
*/
예시 2 - 렉시컬 환경 & 스코프 체인
let a = 1;
function outer() {
let b = 2;
function inner() {
let c = 3;
console.log(a, b, c);
}
innter();
}
outer();
/*
inner Lexical Environment
↓
outer Lexical Environment
↓
global Lexical Environment
*/
실행 컨텍스트 구조 그림
Execution Context
├─ Lexical Environment
│ ├─ Environment Record
│ └─ Outer Environment Reference
├─ Variable Environment
└─ This Binding
꼬리질문
실행 컨텍스트 생성 시 어떤 일이 발생하나요?
스코프 환경 생성
변수/함수 선언 등록 → 호이스팅
this 바인딩 결정
부모 환경(스코프 체인) 연결 → 이후 실행 단계에서 코드 실행
스코프 체인과 실행 컨텍스트의 관계는?
실행 컨텍스트 내부의 Lexical Environment들이 계층적으로 연결된 구조가 스코프 체인입니다.
각 함수가 생성될 때 자신의 상위 환경을 기억하기 때문에 inner → outer → global 순으로 변수 탐색이 가능합니다.
클로저와 실행 컨텍스트의 관계는?
클로저는 내부 함수가 상위 실행 컨텍스트의 변수를 실행이 끝난 이후에도 계속 참조할 수 있는 현상입니다.
즉, 상위 Lexical Environment가 GC되지 않고 유지된 채 내부 함수와 연결되어 있는 상태입니다.
자바스크립트에서 프로토타입(Prototype)은 객체가 다른 객체의 속성과 메서드를 상속(공유)받기 위한 메커니즘입니다. 모든 객체는 내부적으로 [[Prototype]]이라는 숨겨진 슬롯을 가지고 있고, 이 슬롯이 다른 객체를 참조하면서 프로토타입 체인(Prototype Chain)을 형성합니다. 어떤 프로퍼티를 찾을 때 현재 객체에 없으면, 이 프로토타입 체인에 따라 상위 객체에서 계속 탐색합니다.
키워드 설명
Prototype(프로토타입)
자바스크립트의 상속 구조를 구현하는 방식
모든 객체는 [[Prototype]]이라는 내부 슬롯을 가짐
이는 연결된 다른 객체를 가리킴
프로토타입 체인(Prototype Chain)
객체가 특정 프로퍼티를 가지지 않으면 자신의 프로토타입 → 그 상위 프로토타입 → … → Object.prototype 순으로 탐색하는 구조
__proto__ vs prototype
__proto__
객체 인스턴스가 가지고 있는 실제 프로토타입 참조
prototype
함수 객체만 가지는 프로퍼티로, 해당 함수를 new로 호출했을 때 생성되는 객체의 프로토타입이 됨
this는 함수가 호출될 때 자신이 속하게 될 객체를 가리키는 특별한 키워드입니다. 함수의 선언 위치에 따라 스코프가 정적으로 결정되는 렉시컬 스코프와 달리, this는 함수가 ‘어떻게’, ‘어디서’ 호출되었는지에 따라 동적으로 바인딩 되는 것이 특징입니다. 따라서 같은 함수라도 호출 방식에 따라 this가 가리키는 객체는 달라집니다.
키워드 설명
this는 렉시컬 스코프가 아니다.
스코프는 선언 위치로 결정
하지만 this는 호출 시점에 결정 → dynamic binding
this 바인딩 4가지 규칙
기본 바인딩 (Default Binding)
그냥 func() 호출
non-strict → window
strict → undefined
암시적 바인딩 (Implicit Binding)
obj.func()처럼 객체가 “호출 주체”일 때
this = obj
명시적 바인딩 (Explicit Binding)
call , apply , bind 로 this를 강제 지정
call/apply → 호출 시점에만 적용
bind → this가 영구적으로 고정된 새로운 함수 반환
new 바인딩 (Constructor Binding)
new Foo()
this는 새로 생성된 인스턴스를 가리킴
화살표 함수의 this(Lexical this)
화살표 함수는 자신만의 this를 가지지 않음
따라서 선언된 위치의 상위 스코프 this(lexical this)를 그대로 사용
호출 방식(call/apply/bind)으로도 바뀌지 않음
setTimeout 내부에서 this가 깨지는 이유
일반 함수 사용 시, 콜백을 브라우저가 단독 함수 호출(callback())형태로 실행
화살표 함수 사용 시, this는 say()의 this = user로 고정
자료
우선순위
규칙
예시
1
new 바인딩
new Foo()
2
bind 바인딩(명시적)
foo.bind(obj)
3
call/apply 바인딩(명시적)
foo.call(obj)
4
암시적 바인딩
obj.foo()
5
기본 바인딩
foo()
꼬리질문
this는 렉시컬 스코프인가요?
아니요.
자바스크립트의 this는 렉시컬 스코프가 아니라, 호출 방식에 따라 동적으로 결정되는 값입니다
렉시컬 스코프는 선언 위치로 스코프가 고정되지만, this는 어떻게 호출되었는지에 따라 달라집니다.
화살표 함수는 왜 lexical this를 갖나요?
화살표 함수는 자신만의 this 바인딩을 만들지 않도록 설계된 문법입니다.
따라서 호출 방식과 관계없이, 선언된 위치의 상위 스코프 this를 그대로 사용합니다.
이것은 콜백 내부에서 this가 자주 깨지던 문제를 해결하기 위한 ES6의 의도입니다.
setTimeout 안에서 this가 왜 window로 떨어지나요?
일반 함수로 콜백을 전달할 경우, 브라우저(Web API)가 이 콜백을 단독 함수 호출 형태로 실행하기 때문입니다.
단독 호출은 기본 바인딩 규칙을 적용하므로, this는 window(또는 strict 모드에서는 undefined)가 됩니다.
클로저는 함수가 선언될 때의 렉시컬 스코프(외부 변수)를 기억하고, 그 함수가 스코프 밖에서 실행되더라도 그 변수에 접근할 수 있는 기능입니다.
키워드 설명
자바스크립트는 렉시컬 스코프(정적 스코프)를 사용한다.
함수가 정의된 위치를 기준으로 스코프가 결정됨
실행 위치가 아니라 선언된 위치가 핵심
함수는 자신이 선언된 스코프를 기억한다.
내부 함수가 외부 함수의 변수에 접근 가능
외부 함수가 종료된 후에도 그 변수들이 “사라지지 않음”
이 매커니즘을 클로저라고 한다.
실행 컨텍스트가 종료되더라도 내부 함수가 참조하고 있다면 GC로 지워지지 않고 유지됨
예시 코드
function outer() {
let count = 0; // 외부 변수
return function inner() {
count++;
console.log(count); // 외부 변수 사용
}
}
const counter = outer();
counter(); // 1
counter(); // 2
counter(); // 3
outer()는 이미 실행이 끝났지만
inner()가 count를 계속 기억하고 있기 때문
즉, inner가 외부 스코프(outer)를 계속 유지 → 클로저
꼬리질문
렉시컬 스코프란 무엇인가요?
렉시컬 스코프는 함수가 호출되는 위치가 아니라, ‘선언된 위치’를 기준으로 스코프가 결정되는 방식입니다.
자바스크립트는 렉시컬 스코프를 사용하기 때문에 클로저가 가능합니다.
클로저의 단점은 무엇인가요?
클로저는 외부 변수를 계속 참조하므로, 사용이 잘못되면 메모리 해제가 지연되거나 불필요한 메모리 유지로 이어질 수 있습니다.
즉, 메모리 누수 위험이 있다는 것이 단점입니다.
실무에서 클로저를 어디에 사용하나요?
클로저는 함수가 만들어질 당시의 변수들을 계속 기억하게 해줘서, 이벤트, 비동기, 상태 관리, 데이터 은닉 같은 실무 상황에서 필수적으로 쓰입니다.
예를 들어, 1) 버튼클릭 횟수 세기, 2) API 요청 시 “당시의 값”을 기억해야할 때, 3) private 데이터 만들기, 4) React의 setState 내부 동작에 사용됩니다.
자바스크립트는 웹 브라우저에서 동작하도록 만들어진 고수준의 인터프리터 기반 언어입니다. 동적 타입을 사용하고 함수형, 객체지향 등 멀티 패러다임을 지원합니다. 단일 스레드 기반이지만 이벤트 루프를 통해 비동기로 동작합니다. 현재는 Node.js 환경 덕분에 브라우저뿐 아니라 서버와 모바일 개발까지 확장된 범용 언어입니다.
키워드 설명
고수준(High-Level) 언어
메모리 관리나 하드웨어 세부사항을 신경 쓰지 않고 프로그래밍 가능
동적 타입(Dynamically Typed) 언어
변수의 타입이 런타임에 결정됨
let a = 10; a = 'text'; 이런 코드 가능
인터프리터 기반(비컴파일) 언어
코드를 브라우저 엔진(V8 등)이 바로 해석하여 실행
빌드 없이 빠르게 실행되는 특징
멀티 패러다임 지원
절차적 프로그래밍
객체지향(OOP)
함수형 프로그래밍(FP)
→ 현대 JS는 함수형 + 선언적 스타일을 많이 사용
단일 스레드 기반 + 비동기 이벤트 루프 모델
JS는 싱글 스레드
하지만 비동기는 이벤트 루프가 처리하여 성능 향상
브라우저를 위한 언어 → 지금은 범용 언어
원래는 웹페이지 동적 UI를 위해 태어남
그러나 현재는 다음 영역까지 확장
Node.js (백엔드)
React Native(모바일 앱)
Electron (데스크톱 앱)
IoT
서버리스
꼬리질문
왜 자바스크립트는 싱글 스레드인가요?
💡 자바스크립트는 브라우저 환경에서 UI 조작을 안전하게 하기 위해 싱글 스레드 모델을 채택했습니다.
💡여러 스레드가 동시에 DOM을 변경하면 일관성과 성능 문제가 발생할 수 있기 때문입니다.
자바스크립트의 비동기는 어떻게 동작하나요?
💡 자바스크립트는 싱글 스레드이지만, 브라우저나 Node.js의 백그라운드가 비동기 작업을 처리합니다.
💡 완료된 콜백을 이벤트 루프가 큐에서 꺼내 콜스택으로 넣는 방식으로 비동기를 구현합니다.
JS 엔진 (Call Stack)
자바스크립트 코드 자체를 실행하는 스레드(싱글)
Web APIs / Node APIs
setTimeout, fetch, DOM 이벤트 등을 백그라운드에서 처리
JS 엔진이 아님
Callback Queue (Task Queue / Microtask Queue)
비동기 작업이 완료되면 콜백이 큐로 이동
Microtask: Promise then
Macrotask: setTimeout 등
Event Loop
콜스택이 비는 순간, 큐에서 콜백을 꺼내 실행
“JS는 비동기를 스레드가 아니라 스케줄링으로 처리한다”는 의미
자바스크립트는 인터프리터 언어인데, V8은 JIT컴파일을 사용하는 이유는?
💡 전통적으로 자바스크립트는 인터프리터 언어였지만, 실행속도를 높이기 위해 현대 엔진(V8 등)은 코드를 인터프리터로 읽은 뒤, 반복 사용되는 코드를 JIT 컴파일러로 기계어로 변환합니다.