자바스크립트 실행 컨텍스트(Execution Context)
들어가기 전에
자바스크립트에서 대표적으로 헷갈리는 개념들은 closure
, hosting
등이 있습니다.
이러한 개념들을 하나씩 공부하다 보면 도대체 어떤 이유로 동작하는지 이해하기 쉽지 않을 겁니다.
즉, 기본적인 자바스크립트의 내부 동작 흐름을 모르기 때문인데요.
그래서 이번 글에서는 자바스크립트를 이해하기 위해 반드시 알아야 할 실행컨텍스트에 대해 다뤄보겠습니다.
그래서 실행 컨텍스트는 어떤 건가요?
실행 컨텍스트(Execution Context)는 코드의 런타임 값을 추적하기 위해 사용되는 개념이며 진행률을 추적하기 위해 필요한 구현 특정 상태를 포함합니다.
여기서 추적하기 위해 필요한 구현 특정 상태는 다음과 같습니다.
- code evaluation state : 해당 실행 컨텍스트와 연결된 코드의 평가를 수행, 일시 중단 및 재개하는 데 필요한 모든 상태 (code evaluation는 다양한 지점에서 일시 중단되고 다른 컨텍스트의 code evaluation이 진행될 수 있기 때문에 상태를 저장해야 합니다.)
- Function : 실행 컨텍스트가 함수 개체의 코드를 평가하는 경우 사용(활성 객체라고도 불림)
- Realm : 연결된 코드가 ECMAScript 리소스(글로벌 객체, 글로벌 환경 레코드 등)에 액세스 하는 영역 레코드
- LexicalEnvironment : 실행 컨텍스트 내에서 코드로 만든 식별자 참조를 해결하는 데 사용되는 환경 레코드를 식별
- VariableEnvironment : 실행 컨텍스트 내에서 VariableStatement로 생성된 바인딩을 보관하는 환경 레코드를 식별
- PrivateEnvironment : ClassElements에서 만든 private 접근자가 사용된 변수, 메서드들을 보관하기 위해 만든 환경 레코드
여기서 실행컨텍스트 내부를 이해하기 위해 중요한 개념인 LexicalEnvironment와 VariableEnvironment에 대해 세부적인 설명을 추가해보도록 하겠습니다.
Lexical Environment의 구조를 코드로 바꿔서 다시 표현해보면 다음과 같습니다.
Lexical Environment {
EnvironmentRecord: {
type: DeclartiveER | ObjectER,
...
},
outerLexicalEnvironemnt: [Lexical Environment]
}
여기서 declarative environment records는 변수 및 함수 선언의 값을 저장하고 object environment records는 객체를 매핑시켜주며 with문이나 전역 환경에서 사용됩니다.
사실 LexicalEnvironment와 VariableEnvironment은 같은 타입입니다. 즉, 가질 수 있는 정보의 타입이 동일합니다.
두 개의 차이점은 다음과 같습니다.
- LexicalEnvironment은 지속적으로 식별자를 업데이트해주지만 VariableEnvironment은 선언으로 만들어진 값을 유지합니다. 이를 통해 임시 범위를 가지는 with문을 사용하고 변경된 LexicalEnvironment를 임시 범위를 벗어나게 되면 VariableEnvironment를 통해 이전 값을 복원할 수 있습니다.
- 함수 선언은 VariableEnvironment, 함수 표현식은 LexicalEnvironment를 사용합니다.
- 블록 스코프를 기준으로 하는 const, let은 LexicalEnvironment에 속합니다.
위 상태를 미뤄봤을 때 실행 컨텍스트는 식별자, 글로벌 환경 레코드, 메서드 정보 등 다양한 정보를 가지고 있습니다.
따라서 실행 컨텍스트는 실행 가능한 코드를 실행하기 위해 필요한 환경을 가지고 있는 것을 알 수 있습니다. (실행 가능한 코드는 eval, 메서드, 전역코드, 블럭 코드가 있다.)
function foo() {
bar();
}
function bar() {
console.log('hi');
}
foo()
또한 우리가 흔히 알고 있는 호출 스택에 포함된 항목은 실행컨텍스트로 호출 스택 = "실행 컨텍스트 스택"으로 이해하셔도 됩니다.
+) 추가
사실 실행 컨텍스트에 대해 찾아보게 되면 위 사진처럼 크게 3가지로 나눠 설명하는 경우도 있는데 이는 ES5 이전까지 사용된 개념이고 ES5 이후부터는 앞에서 설명한 개념으로 내용이 변경됐습니다. 해당 개념으로 이해해도 크게 상관없지만(오히려 이해하기 쉽습니다.) "지금은 이렇게 바뀌었다." 정도로 이해해주시면 되겠습니다 :)
그럼 이제 어떻게 동작하는 건가요?
실행 컨텍스트의 생성과정은 크게 2가지로 나눌 수 있습니다.
1. Creation Phase
2. Execution Phase
Creation Phase
해당 단계에서는 코드가 실행하기 전에 변수나 함수들을 미리 선언하고 초기화하는 단계입니다.
creation phase에서는 전체 코드가 2번 실행됩니다.
1. 함수를 선언 & 초기화
2. 모든 변수를 undefined로 할당(VariableEnvironment 내에 있는 변수 한정)
2번 실행됨으로써 LexicalEnvironment, VariableEnvironment 컴포넌트가 생성되고 내부 값이 할당됩니다.
var hello = 'hello';
function foo() {
bar();
function bar() {
console.log(hello);
}
}
foo()
해당 코드를 기준으로 Creation Phase를 진행하면 다음과 같이 진행됩니다.
- 전역 코드에 대한 Creation Phase
- 전역 코드에 대한 Execution Phase
- foo에 대한 Creation Phase
- foo에 대한 Execution Phase
- bar에 대한 Creation Phase
- bar에 대한 Execution Phase
전역 코드에 대한 Creation Phase에서는 기본적인 선언은 되었지만 hello 변수 같은 경우에는 undefined로 선언됐습니다.
Execution Phase
해당 단계에서는 우리가 인지하고 있던 실행이라는 단계가 해당 단계에서 시작합니다.
Execution Phase에서는 선언했던 변수에 값을 할당해줍니다.
따라서 전역 코드에 대한 Execution Phase에서는 선언했던 변수 hello에 값을 할당해줍니다.
최종적으로 아래와 같은 구조를 이루게 됩니다.
이걸로 다른 개념을 설명할 수 있나요?
이제 실행 컨텍스트가 무엇인지 어떻게 동작하는지 대략적으로 알아봤으니 실행 컨텍스트를 통해 다른 개념도 알아보도록 하겠습니다.
먼저 호이스팅부터 알아보겠습니다.
호이스팅이란 실행컨텍스트가 생성될 때 렉시컬 스코프 내에서 변수, 함수를 최상단으로 끌어올려진 것처럼 느끼게 해주는 개념입니다.
사실 Creation Phase를 설명하면서 호이스팅이 어떻게 돌아가는지 확인할 수 있었는데요.
Creation Phase는 코드 실행 전에 전체 코드에 대해 2번 돌면서 함수, 변수 선언 및 초기화를 해줍니다. 이를 통해 코드가 실행하기 전에
식별자 선언을 이미 끝냈기 때문에 호이스팅이 가능한 겁니다.
그다음으로는 클로저입니다. 클로저에 대한 예시는 아래와 같습니다.
function foo() {
const hello = 'hello';
return function() {
return hello + 'nemne';
}
}
const bar = foo();
bar(); // expected result : 'hello nemne'
bar()가 실행되는 기준으로 만든 실행컨텍스트의 구조입니다.
그림을 보시면 foo() 메서드는 이미 종료된 메서드이지만 bar메서드의 Lexical Environment를 보면 outer가 foo메서드의 Lexical Environment를 가리키고 있습니다. 따라서 해당 LexicalEnv에 있는 hello 변수를 참조하여 사용할 수 있는 겁니다.