Skip to Content
BlogJavaScript 정리기초 정리JS 동작원리 1 (호이스팅, 실행 컨텍스트, 콜스택&힙, 전역)

JS 동작원리 1 (호이스팅, 실행 컨텍스트, 콜스택&힙, 전역)

호이스팅과 TDZ (Temporal Dead Zone)

호이스팅은 변수와 함수 선언이 코드가 실행되기 전, 해당 스코프의 최상단으로 끌어올려지는 것처럼 동작하는 JS의 특성이다. 실제로 코드 위치가 바뀌는 건 아니고, JS 엔진이 코드를 실행하기 전에 미리 변수/함수 선언을 스코프에 등록해두기 때문에 이런 현상이 생긴다.

함수 선언식은 통째로 호이스팅된다

console.log(sayHi()); // 정상 동작 function sayHi() { return "hi"; }

함수 선언식은 함수 전체(이름 + 내용)가 호이스팅되기 때문에, 선언보다 위에서 호출해도 문제없이 동작한다.

var는 선언만 호이스팅되고 undefined로 초기화된다

console.log(name); // undefined (에러 아님) var name = "gori"; console.log(name); // gori

var name = "gori";는 내부적으로 var name;(선언, 호이스팅됨)과 name = "gori";(할당, 원래 위치에서 실행)로 나뉘어 동작한다고 이해하면 된다.

let, const는 호이스팅되지만 TDZ에 갇혀있다

console.log(age); // ReferenceError: Cannot access 'age' before initialization let age = 20;

let, const도 사실 호이스팅은 된다. 다만 var처럼 undefined로 초기화되는 게 아니라, 스코프 시작 지점부터 실제 선언문에 도달하기 전까지는 접근 자체가 금지된 상태에 놓인다. 이 구간을 **TDZ(Temporal Dead Zone, 일시적 사각지대)**라고 부른다.

{ // 이 지점부터 TDZ 시작 console.log(score); // ReferenceError let score = 100; // 이 지점에서 TDZ 종료 console.log(score); // 100 }

TDZ는 var의 “호이스팅됐지만 undefined라서 에러 없이 조용히 넘어가는” 문제를 막기 위해 도입됐다. 변수를 선언 전에 사용하는 실수를 에러로 명확하게 잡아준다.

호이스팅 정리

구분호이스팅 여부선언 전 접근 시
함수 선언식전체(내용까지) 호이스팅정상 호출 가능
var선언만 호이스팅undefined 반환
let, const선언만 호이스팅(TDZ 적용)ReferenceError


실행 컨텍스트 (Execution Context)

실행 컨텍스트는 코드가 실행되는 데 필요한 환경 정보를 담은 객체다. JS 엔진은 코드를 실행할 때마다 실행 컨텍스트를 생성하고, 그 안에 필요한 정보들을 채워 넣는다.

실행 컨텍스트의 종류

  • 전역 실행 컨텍스트 (Global Execution Context): 코드가 실행되면 가장 먼저 생성되며, 프로그램 전체에 딱 하나만 존재한다.
  • 함수 실행 컨텍스트 (Function Execution Context): 함수가 호출될 때마다 새로 생성된다.

실행 컨텍스트가 만들어지는 두 단계

  1. 생성 단계: 변수/함수 선언을 스코프에 등록(호이스팅), this 바인딩 결정, 스코프 체인 구성
  2. 실행 단계: 코드를 한 줄씩 실제로 실행하며 변수에 값을 할당
const name = "gori"; function greet() { const message = "안녕하세요"; console.log(`${message}, ${name}`); } greet();

이 코드가 실행되는 흐름은 다음과 같다.

  1. 전역 실행 컨텍스트 생성 → name, greet 함수 호이스팅
  2. 코드 실행 → name에 “gori” 할당
  3. greet() 호출 → 함수 실행 컨텍스트 새로 생성 → message 호이스팅
  4. 함수 내부 코드 실행 → message에 값 할당, console.log 실행
  5. 함수 실행이 끝나면 해당 함수 실행 컨텍스트는 제거됨 앞서 다룬 스코프 체인, 클로저, this 바인딩이 모두 이 실행 컨텍스트 생성 단계에서 결정되는 정보들이다.


콜 스택 & 힙 (Call Stack & Heap)

콜 스택 (Call Stack)

함수가 호출되는 순서를 기록하는 자료구조다. 이름 그대로 스택(LIFO, 후입선출) 구조라서, 가장 나중에 쌓인 함수가 가장 먼저 실행 완료되고 제거된다.

function first() { second(); console.log("first 종료"); } function second() { third(); console.log("second 종료"); } function third() { console.log("third 실행"); } first();

콜 스택이 쌓이고 제거되는 과정

1. first() 호출 → [first] 2. second() 호출 → [first, second] 3. third() 호출 → [first, second, third] 4. third() 실행 완료 → [first, second] ("third 실행" 출력) 5. second() 실행 완료 → [first] ("second 종료" 출력) 6. first() 실행 완료 → [] ("first 종료" 출력)

JS가 싱글 스레드인 이유가 바로 이 콜 스택이 하나이기 때문이다. 콜 스택에 작업이 쌓여있으면 그 작업이 끝날 때까지 다른 코드는 실행되지 못하고 대기한다. 만약 함수가 끝없이 자기 자신을 호출하면(재귀 함수의 종료 조건이 없는 경우) 콜 스택이 넘쳐서 RangeError: Maximum call stack size exceeded 에러가 발생하는데, 이를 스택 오버플로우라고 한다.

힙 (Heap)

객체, 배열, 함수처럼 크기가 고정되지 않은 참조 타입의 데이터가 저장되는 메모리 공간이다. 콜 스택이 함수 호출 순서를 관리하는 정돈된 구조라면, 힙은 이름 그대로 구조화되지 않은 넓은 저장 공간에 가깝다.

const user = { name: "gori" }; // { name: "gori" } 객체 자체는 힙에 저장 // user 변수에는 힙에 저장된 객체의 참조(주소)가 담김

이 구조는 “변수와 자료형” 글에서 다룬 원시 타입 vs 참조 타입의 메모리 저장 방식 차이와 이어지는 내용이다.



전역 객체 (window, globalThis)

전역 객체는 코드가 실행되는 환경에서 최상위에 존재하는 객체로, 별도로 선언하지 않아도 어디서든 접근할 수 있다.

환경마다 다른 이름을 가진다

// 브라우저 환경 console.log(window); // 브라우저의 전역 객체 // Node.js 환경 console.log(global); // Node.js의 전역 객체

환경마다 전역 객체의 이름이 달라서 코드 호환성 문제가 있었는데, 이를 해결하기 위해 ES2020에서 환경에 상관없이 동일하게 사용할 수 있는 globalThis가 도입됐다.

console.log(globalThis); // 브라우저에서는 window, Node.js에서는 global과 동일

전역에서 var로 선언한 변수는 전역 객체의 속성이 된다

var name = "gori"; console.log(window.name); // "gori" (var로 선언한 전역 변수는 window의 속성이 됨)

let, const로 선언한 전역 변수는 전역 객체의 속성이 되지 않는다

let age = 20; console.log(window.age); // undefined

이 차이도 let, const가 var보다 예측 가능한 스코프 관리를 제공하는 이유 중 하나다. 전역 객체를 오염시키지 않기 때문이다.

전역에서 this는 전역 객체를 가리킨다

console.log(this === window); // true (브라우저, 전역 스코프에서)


마지막 수정