JavaScript 실행 컨텍스트
Goal
자바스크립트에서 작성된 코드는 어떻게 실행될까? 내가 작성한 코드에 변수를 선언하고 값을 대입했다면, 그 변수는 어디서, 어떤 방법으로 가져오게 될까? 어떤 규칙으로? 어디에 그것이 정해져 있는 것일까? 자료를 찾아보고 이해하자.
실행 컨텍스트
자바스크립트 코드가 실행되는 상황을 상상해보자.
자바스크립트 코드가 브라우저에서 실행된다고 가정하면, 작성한 코드를 브라우저가 이해할 수 있는 기계어로 변환하는 과정이 필요하다!
브라우저의 렌더링 과정에 따르면, 브라우저가 자바스크립트 코드를 발견하면 자바스크립트 엔진에게 코드를 보낸다. 자바스크립트 엔진은 전달받은 자바스크립트 코드를 변환하고 실행하기 위해 특수한 환경을 생성한다. 이 환경을 실행 컨텍스트**라고 부른다.
- 실행 컨텍스트에는 실행 중인 코드 및 실행에 도움이 되는 정보들을 모여있다.
- 변수나 함수의 실행 컨텍스트는 변수나 함수가 어떻게 행동하는지를 규정한다.
- 💥자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 끌어올리고, 외부 환경 정보를 구성하고, this 값을 설정하는 등 동작을 수행하게 된다. 그래서 다른 언어에서는 발견할 수 없는 특이한 현상들이 발생하기도 한다.
실행 컨텍스트가 무엇인지 알았으니 그 동작을 통해 자바스크립트에서 소스코드가 어떻게 실행되는지 알아보자.
생성과 실행
앞서 자바스크립트 엔진이 코드를 실행하기 위해 실행 컨텍스트를 생성한다고 했다.
실행 컨텍스트가 생성되고 실행될 때 어떤 동작이 이뤄지는지 확인해보자.
생성 단계
실행 컨텍스트는 3가지 프로퍼티를 소유한다.
- Variable Object 변수 객체
- 변수
- 매개변수(paramter), 인수(arguments)
- 함수 선언(함수 표현식은 제외)
- Scope chain 스코프 체인
- this value
먼저 실행 컨텍스트 생성 단계에는 Variable Object(VO)라는 것이 만들어진다. 해당 실행 컨텍스트 내에 정의된 변수와 함수 선언을 VO에 저장한다.
변수 선언은 VO 메모리의 속성으로 저장되며 undefined
로 초기화, 함수는 VO의 메모리에 저장이 된다.
이렇게 코드를 실행하기 전에 변수와 함수의 선언을 저장하는 과정을 호이스팅이라고 한다. 호이스팅. 많이 들어봤다. 좀 더 알아보자.
호이스팅 (Hoiisting)
네이버에 "hoisting"을 검색해보면 사전적 의미가 나온다:
hoisting: 끌어 올리기, 들어올려 나르기
console.log(message);var message = 1;
자바스크립트를 처음 배울 때 위와 같은 코드를 보면 message
라는 변수를 선언하기 전에 불러왔으므로 오류가 날 것이라 생각했다. 하지만 undefined
가 출력된다.
왜 오류가나지 않고 이런 값이 나오는 걸까?
앞서 실행 컨텍스트가 생성될 때 함수 및 변수의 선언은 호이스팅되어 실행 컨텍스트의 VO라는 곳에 저장된다고 했다. 그래서 코드를 실행하기 전에도 사용이 가능하다. 따라서 참조 오류가 나지 않고 출력이되는 것이다.
코드는 다음과 같이 처리가 된다:
var message; // 선언문을 찾아 먼저 처리한다.console.log(message); // 값이 대입되지 않았으므로 undefined 출력message = 1; // 대입된다.
위 코드처럼 변수 선언문이 위로 올려진 것처럼 끌어올린다는 의미의 호이스팅이 자바스크립트의 특징이다.
자바스크립트는 모든 선언(var
, let
, const
, function
)을 호이스팅한다. 참고
foo(); // 함수도 호이스팅function foo() {console.log('hello')}
스코프 체인 생성
VO 생성 후 스코프 체인이 생성된다.
각 함수 실행 컨텍스트는 "스코핑"이라는 프로세스를 통해 정의된 변수와 함수에 액세스할 수 있는 환경을 만든다. 이 환경은 코드베이스 내에서 코드 조각이 있는 위치를 의미한다.
함수가 다른 함수에서 정의되면 내부 함수는 외부 함수와 그 부모의 코드에 정의된 코드에 접근할 수 있다. 이런 작동 방식을 렉시컬 스코프라고 한다.
this 값이 설정된다.
실행 컨텍스트 생성 단계에서 스코프를 지정한 후 마지막으로 this의 값을 설정하는 것이다.
스코프 체인이 생성되면, 자바스크립트 엔진에 의해 this
의 값이 초기화된다. this
는 실행 컨텍스트가 속한 범위를 나타낸다.
실행 단계
생성 단계가 끝나면 선언문을 제외한 소스코드가 순차적으로 실행되기 시작한다. (런타임이 시작된다.) 이때 소스코드 실행에 필요한 정보, 즉 변수나 함수의 참조를 실행 컨텍스트가 관리하는 스코프에서 검색해서 취득한다. 그리고 변수 값의 변경 등 소스코드의 실행 결과가 다시 실행 컨텍스트가 관리하는 객체에 등록이된다.
예시와 함께 실행 컨텍스트 생성 & 코드 실행을 살펴보자:
var x;x = 1;
위 코드는 아래 그림과 같이 실행 컨텍스트를 통해 처리된다. 우선 자바스크립트 엔진이 실행 컨텍스트 생성 과정에서 변수 선언문 var x;
를 먼저 실행하고 이때 생성된 식별자 x
가 실행 컨텍스트가 관리하는 스코프(VO)에 등록되어 undefined
로 초기화된다.
그리고 실행 과정이 시작된다. 이때 실행 컨텍스트가 관리하는 스코프에 x
가 등록되어 있는지 확인을 한다. 등록되어 있다면 선언된 변수이므로 값을 할당하고 할당 결과를 다시 실행 컨텍스트에 등록해 관리한다.
실행 컨텍스트 스택 (콜 스택)
자바스크립트는 싱글 스레드이기 때문에 한 번에 하나의 작업만 실행 가능하다. 따라서 함수가 실행되거나 이벤트가 발생하면 각각에 대해 실행 컨텍스트가 생성되어 스택으로 관리된다.
이를 실행 컨텍스트 스택이라 부르는데, 콜 스택이라고 부르기도 한다. 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.
여기서 말하는 컨텍스트를 구성하는 '동일한 환경'에는 다음과 같은 것들이 있다.
- 전역 코드: 전역에 존재하는 코드
- 함수 코드: 함수 내부에 존재하는 코드
- eval 코드:
eval
함수에 인수로 전달되어 실행되는 코드 - 모듈 코드: 모듈 내부에 존재하는 코드
다음 코드를 살펴보자:
const x = 1;const y = 2;function sum(a, b) {const x = 100;const y = 200;console.log(x + y + a + b);}sum(x, y); // 303console.log(x + y); // 3
- 앞에서 살펴본 것과 같이 자바스크립트 엔진은 우선 전역 코드를 위한 실행 컨텍스트를 생성한다. 선언문을 먼저 실행해 그 결과가 실행 컨텍스트가 관리하는 전역 스코프에 등록한다.
- 전역 실행 컨텍스트가 생성되고 나서 전역 코드가 순차적으로 실행된다. 이때 변수에 값이 할당되고 함수가 호출된다. 함수가 호출되면, 전역 코드의 실행을 일시 중단하고 코드 실행 순서를 변경해 함수 내부로 진입한다.
- 함수 내부로 진입하면 함수 내부의 문들을 실행하기 전에
sum
함수의 실행 컨텍스트가 생성된다. 이때 매개변수와 지역 변수 선언문이 먼저 실행되고, 그 결과가 실행 컨텍스트가 관리하는 지역 스코프에 등록된다. 함수 내부에서는arguments
객체가 생성되어 실행 컨텍스트가 관리하는 지역 스코프에 등록되고, this 바인딩도 결정된다. sum
함수의 실행 컨텍스트가 생성되면 함수 코드가 순차적으로 실행된다. 값이 할당되고 여기서는console.log
메서드를 호출하기 위해 식별자인console
을 스코프 체인을 통해 검색한다.console.log
메서드가 실행되고 나면, 함수 호출 이전으로 되돌아가 전역 코드 실행을 이어서 한다.