일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- JavaScript
- react
- 클린코드
- context.api
- vite
- 아키텍처
- 웹워커
- 프론트엔드
- 회고
- radixui
- 티스토리챌린지
- frontend
- 자바스크립트
- 오블완
- MicroFrontEnd
- 리액트
- CRA
- Web
- 이것저것
- 에세이
- server component
- MFA
- sharedworker
- TypeScript
- 리팩토링
- CustomHook
- 합성 컴포넌트
- provider 패턴
- Webworker
- virtaullist
- Today
- Total
Lighthouse of FE biginner
[JavaScript] 실행 컨텍스트 본문
Overview
자바스크립트는 소스코드를 4가지 타입으로 구분한다.
- 전역 코드
- 전역에 존재하는 소스코드, 전역에 정의된 함수, 클래스 등 내부 코드는 포함되지 않는다.
- 전역 스코프를 생성하며 전역 코드가 평가되면 전역 실행 컨텍스트가 생성된다.
- 함수 코드
- 함수 내부에 존재하는 소스코드, 함수 내부에 중첩된 함수, 클래스 등의 내부 코드는 포함하지 않는다.
- 지역 스코프를 생성하며 지역변수, 매개변수, arguments 객체를 관리한다. 생성한 지역 스코프를 전역 스코프에서 시작하는 스코프 체인의 일원으로 연결한다. 함수 코드가 평가되면 함수 실행 컨텍스트가 생성된다.
- eval 코드: eval 함수에 인수로 전달되어 실행되는 소스코드
- 모듈 코드: 모듈 내부에 존재하는 소스코드, 모듈 내부의 함수, 클래스 등 내부 코드는 포함하지 않는다.
소스코드의 평가와 실행
자바스크립트 엔진은 소스코드를 평가와 실행 단계로 나눠서 처리한다. 코드의 평가 단계에서는 실행 컨텍스트가 생성되고 작성된 변수, 함수, 클래스 등 선언문이 실행이 되어(호이스팅) 실행 컨텍스트의 렉시컬 환경의 환경 레코드에 등록이 된다.
코드의 평가 단계가 끝나면 실행 단계(런타임)이 시작되는데 이때 소스코드를 읽어나가며 필요한 정보를 실행 컨텍스트에서 관리하는 스코프에서 검색해 얻게된다. 소스코드의 실행 결과는 실행 컨텍스트에서 관리하는 스코프에 등록이 된다.
아래 코드를 살펴보며 소스코드가 어떻게 실행이 되는지 예상해보자.
var a = 1;
var b = 2;
const d = "c";
function foo() {
return a + b;
}
var c = foo();
console.log(c);
먼저 자바스크립트 엔진이 소스코드 평가를 시작한다. 전역 객체가 생성이 된다. (window, global)
여기서 주의해야 할 점은 전역 객체가 전역 실행 컨텍스트가 아니라는 점이다. 전역 객체는 전역 실행 컨텍스트가 생성되기 이전에 생성된다.
전역 실행 컨텍스트를 생성하고 렉시컬 환경에 전역 환경 레코드가 바인딩 된다.
선언된 식별자를 전역 환경 레코드에 등록한다. 여기서 a, b, c 식별자의 선언과 암묵적 초기화가 발생하고 foo 함수 객체가 생성이 되어 메모리에 암묵적으로 foo 라는 식별자가 생성이되어 해당 함수 객체를 바라본다. 이때 선언된 전역 변수와 함수 객체는 전역 실행 컨텍스트가 관리하는 전역 스코프에 등록이 되며 전역 객체의 프로퍼티와 메서드가 된다.
렉시컬 환경
렉시컬 환경은 실행 컨텍스트 영역에 어떤 코드들이 작성이 되어있는지에 대한 정보를 담고 있는 컴포넌트다. 즉 식별자와 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록하는 자료구조이다.
렉시컬 환경은 환경 레코드([[EnvironmentRecord]])와 외부 렉시컬 환경 참조(OuterLexicalEnvironmentReference)로 구성된다. 환경 레코드는 해당 렉시컬 환경을 구성하는 식별자와 값을 담고 있으며 외부 렉시컬 환경에 대한 참조 값은 말 그대로 상위 스코프에 대한 참조 값을 가지고 있다.
전역 환경 레코드
전역 환경 레코드는 객체 환경 레코드(Object Environment Record)와 선언적 환경 레코드(Declarative Environment Record)로 구성된다. 전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 전역 객체가 this 바인딩 된다.
객체 환경 레코드
전역 객체가 관리하던 var 키워드로 선언한 전역 변수와 함수 선언문으로 정의한 전역 함수, 빌트인 전역 프로퍼티와 빌트인 전역 함수, 표준 빌트인 객체를 관리한다.
전역 객체 환경 레코드는 전역 객체를 생성할 때 생성된 전역 객체(BindingObject)와 연결된다. 전역 코드 평가 단계에서 var 키워드의 전역 변수와 함수 선언문으로 정의된 전역 함수는 BindingObject를 통해 전역 객체의 프로퍼티와 메서드가 된다.
var 키워드의 전역 변수와 함수 선언문이 전역 객체의 프로퍼티, 메서드가 되는 이유이다.
var 키워드의 전역 변수 코드의 평가 단계에서 선언과 undefined 초기화가 발생하고 전역 객체 환경 레코드에 등록된다.
함수 선언문으로 작성된 전역 함수는 코드의 평가 단계에서 함수 객체가 생성되고 암묵적으로 함수 명의 식별자가 생성이 되어 그 즉시 함수 객체를 할당하고 전역 객체 환경 레코드에 등록된다.
그렇기 때문에 var 키워드의 식별자는 선언문을 만나기 이전에 참조를 할 수 있지만 undefined가 바인딩이 되어있고, 함수 선언문으로 작성된 함수는 선언문을 만나기 이전에 호출을 해도 해당 함수를 호출할 수 있는 것이다.
선언적 환경 레코드
let, const 키워드로 선언한 전역 변수를 관리한다. 즉 let, const로 선언된 식별자는 선언적 환경 레코드에 등록이 된다. 이 말은 let, const 키워드로 선언된 식별자는 전역 객체의 프로퍼티로 등록되지 않는다는 것이다.
const 키워드로 선언된 식별자는 선언 단계와 초기화 단계가 분리된다. 즉 초기화 단계 (런타임에 실행 흐름이 변수 선언문에 도달하기 이전까지) 일시적 사각 지대(TDZ)에 빠지게 된다.
전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this가 바인딩 된다. 여기서의 this 바인딩은 전역 객체인 window, global 이다.
this 바인딩
전역 환경 레코드에 전역 객체가 this 바인딩이 된다. 환경 레코드 내부의 객체 환경 레코드와 선언적 환경 레코드에는 this 바인딩이 발생하지 않는다. this 바인딩은 전역 환경 레코드와 함수 환경 레코드에만 발생한다.
마지막으로 외부 렉시컬 환경에 대한 참조가 결정된다. 전역 소스코드는 자신을 포함하는 소스코드가 없으므로 전역 렉시컬 환경의 외부 렉시컬 환경에 대한 참조는 null이 바인딩 된다. 이는 전역 렉시컬 환경이 스코프 체인의 종점에 존재한다는 것을 의미한다.
전역 실행 컨텍스트는 실행 컨텍스트 스택에 push 된다.
위 그림에서 GlobalThisValue가 전역 렉시컬 환경의 컴포넌트로 오해할 수 있다. GlobalThisValue는 전역 렉시컬 환경의 내부 슬롯이다. ([[ ]] 로 표현한다.)
평가 단계가 끝나고 전역 코드를 실행한다. 스코프에서 a, b, c, d 라는 식별자를 검색해 값을 할당한다.
만약 참조 하려는 식별자가 해당 실행 컨텍스트 (환경 레코드)에 존재하지 않는다면 외부 렉시컬 환경 참조 값에 의해 상위 실행 컨텍스트(상위 스코프)로 이동하여 식별자를 검색한다.
8행에서 foo 실행문을 만나 foo 함수 객체를 실행한다. 이때 전역 코드의 실행이 멈추고 foo 함수 코드 평가를 시작한다.
함수 코드를 평가하면서 함수 실행 컨텍스트가 생성되고, 매개변수와 지역변수 선언문이 실행이 되어 함수 실행 컨텍스트의 환경 레코드에 등록된다.
함수 실행 컨텍스트
함수 실행 컨텍스트는 렉시컬 환경과 외부 렉시컬 환경 참조 값을 갖고, 렉시컬 환경에는 함수 환경 레코드가 바인딩 된다. 함수 환경 레코드에는 함수 내부에 선언된 식별자가 등록이 되며 arguments 객체가 생성이 되어 바인딩 된다.
외부 렉시컬 환경 참조 값은 함수를 실행한 실행 컨텍스트의 렉시컬 환경 참조 값이 바인딩 된다. foo 함수는 전역 실행 컨텍스트에 의해 호출이 되었기 때문에 외부 렉시컬 환경 참조 값으로 전역 렉시컬 환경 참조 값이 바인딩 된다.
렉시컬 환경의 [[ThisValue]]에 this 바인딩이 결정이 된다. 함수는 어떤 방식으로 호출이 되었는지에 따라 this 바인딩이 다르게 결정이 된다. 이 예문에서는 함수 선언문으로 호출이 되었기 때문에 전역 객체로 this 바인딩이 된다.
함수 코드 평가가 종료되고 함수가 실행된다. 함수 실행 컨텍스트에서 a, b 식별자를 검색할 수 없기에 외부 렉시컬 환경 참조 값으로 상위 스코프에서 식별자를 검색한다. 전역 스코프에 등록된 a, b 식별자를 찾고 해당 값을 사용해 값을 반환한다.
함수 실행이 종료되고 실행 컨텍스트 스택에서 Pop 된다. c 식별자에 반환 된 값 (6)이 재할당 된다.
실행 컨텍스트 스택에서 pop 되고 소멸이 됐지만 렉시컬 환경도 무조건 소멸이 되는 것은 아니다.
만약 렉시컬 환경이 다른 렉시컬 환경에 의해 참조가 되고 있다면 해당 렉시컬 환경은 메모리에 남아있게 된다.
위 개념은 다음 스터디 회차 클로저에서 자세하게 살펴보도록 하겠다.

마지막으로 소스 코드의 실행이 끝날 때 실행 컨텍스트 스택에서 전역 실행 컨텍스트를 pop 한다.
위의 모든 과정을 관리하는 것이 실행 컨텍스트이다. 실행 컨텍스트는 소스코드를 실행하는데 필요한 환경을 제공하고 코드의 실행 결과를 관리하는 영역이다. 선언된 식별자와 스코프는 렉시컬 환경으로, 코드의 실행 순서는 실행 컨텍스트 스택으로 관리한다.
실행 컨텍스트와 블록 레벨 스코프
var 키워드는 함수 레벨 스코프를, let const 키워드는 블록 레벨 스코프를 갖는다고 했다. 함수는 실행 컨텍스트를 가지고 있지만 블록은 별도의 실행 컨텍스트가 존재하지 않는다.
그럼 블록 레벨 스코프는 어떤 방식으로 관리가 되는 것일까?
var a = 1;
var b = 2;
{
const a = 1;
const b = "c";
console.log(a + b);
}
위 코드에는 전역 코드에서 코드 블럭으로 인해 블록 스코프가 생성이 된다.
이를 관리하기 위해 선언적 환경 레코드를 갖는 블록 렉시컬 환경을 생성을 하고 전역 렉시컬 환경을 교체한다. 블록 렉시컬 환경은 외부 렉시컬 환경 참조 값으로 기존의 전역 렉시컬 환경을 가리킨다.
코드 블럭의 실행이 종료되면 전역 렉시컬 환경은 다시 기존의 전역 렉시컬 환경을 가리킨다.
'자바스크립트' 카테고리의 다른 글
[JavaScript] this 심화 탐구 (0) | 2025.04.10 |
---|---|
[JavaScript] this 바인딩 (1) | 2024.12.10 |
[JavaScript] eval 메서드에 대하여 (0) | 2024.12.01 |
[JavaScript] var 그리고 let, const (1) | 2024.11.27 |
[JavaScript] 스코프 (2) | 2024.11.24 |