자바스크립트의 코드가 실행되는 원리인 excute context에 대한 내용을 정리해보면서 변수의 유효 범위에 대해서도 함께 이해할 수 있도록 정리해보자.
실행 컨텍스트 (Execute Context )란?
- 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
- 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념
자바스크립트는 동일한 환경에 있는 정보들을 모은 실행 컨텍스트를 Call Stack (콜스택) 에 쌓아올린 후 실행하여 코드의 환경과 순서를 보장할 수 있게 된다. 여기서 환경은 global context (전역 환경) 이 될 수도 있고, function context (함수 내부 환경)이 될 수도 있다.
실행 컨텍스트의 종류
- 전역 실행 컨텍스트 (Global Execution Context)
실행 컨텍스트의 기본이자 기초이다. 함수 밖에 있는 코드들은 전역 실행 컨텍스트에 존재한다. 브라우저의 경우 window 객체를 생성하여, 이를 global object로 설정한다. 프로그램에는 오직 한 개의 전역 실행 컨텍스트만 존재할 수 있다. - 함수 실행 컨텍스트 (Function Execution Context)
함수가 실행되면 해당 함수에 대한 새로운 실행 컨텍스트가 만들어진다. 각 함수는 고유의 실행 컨텍스트를 갖지만, 함수가 실행되거나 호출될 때만 생성된다. 함수 실행 컨텍스트의 수는 제한이 없다. - Eval 실행 컨텍스트 (Eval Execution Context)
eval 함수 내부에서 실행되는 코드도 고유의 실행 컨텍스트를 갖고 있다. 하지만, eval이 자바스크립트 개발자들에게 쓰이는 일이 잘 없는 관계로, 따로 정리하지 않을 예정이다.
실행 컨텍스트의 구성
let a = 1;
function fn2() {
console.log(a); // undefined
let a = 3;
console.log(a); // 3
}
function fn1() {
fn2();
}
fn1();
console.log(a); // 1
자바스크립트 코드를 위와 같이 구성했을 때 실행 컨텍스트의 콜스택은 이미지와 같은 순서로 실행된다.
브라우저의 경우에는 window, node 환경의 경우에는 global 같은 객체를 사용할 수 있게 된다.
- 첫번째 콜스택에는 전역 실행 컨텍스트만 존재하기 때문에 전역 실행 콘텍스트와 관련된 코드를 실행한다.
- 전역 실행 컨텍스트와 관련된 코드를 진행하던 중에 fn1( ) 함수의 호출이 발생하면 fn1( ) 함수의 환경 정보들을 수집하여 fn1( ) 실행 컨텍스트를 생성하고, 이를 콜스택에 담는다. 이때, 콜스택 최상단에는 fn1( ) 실행 컨텍스트가 있기때문에 전역 실행 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한 후 fn1( ) 실행 컨텍스트의 코드를 실행한다.
- fn1( ) 안에 있는 fn2( ) 함수가 호출되면 자바스크립트 엔진은 fn2( ) 함수의 환경 정보들을 수집한 후 fn2( ) 실행 컨텍스트를 생성하여 콜스택에 담는다. 이전과 같이 콜스택 최상단에 fn2( ) 실행 컨텍스트가 있기 때문에 fn1( ) 실행 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한다.
- fn2( ) 함수가 종료되면 fn2( ) 실행 컨텍스트가 콜스택에서 제거된다. 제거 후 콜스택 최상단에는 fn1( ) 실행 컨텍스트가 있으므로 중단되었던 지점부터 코드를 다시 실행한다.
- fn1( ) 함수도 종료되면 fn1( ) 실행 컨텍스트가 콜스택에서 제거된다.
- 모든 코드가 실행되고 나면 자바스크립트 엔진은 콜스택에서 전역 실행 컨텍스트를 제거하고, 콜스택에 아무것도 남지 않은 상태로 종료된다.
그리고 이러한 실행 컨텍스트를 구성할 때 생성되는 것들이 있다.
- Lexical Environment (렉시컬 환경)
- Variable Environment (변수 환경)
- This Binding (This 바인딩)
실행 컨텍스트는 다음과 같이 개념적으로 표현될 수 있다.
ExecutionContext = {
LexicalEnvironment = <ref. to LexicalEnvironment in memory>,
VariableEnvironment = <ref. to VariableEnvironment in memory>,
}
렉시컬 환경 (Lexcial Environment)
렉시컬 환경은 Lexical Scope라고도 한다. Lexical Environment는 함수가 호출될 때를 기반으로 코드의 scope를 파악하는 것이 아니라 쓰여진 것을 기반으로 scope를 파악한다. Lexical Environment는 초기에는 Variable Environment 와 같지만 변경 사항이 실시간으로 적용된다.
즉, Variable Environment 초기 상태를 기억하고 있으며, Lexical Environment 최신 상태를 저장하고 있다.
Lexical Environment의 내부에는 아래의 컴포넌트들로 구성되어 있다.
- Environment Record : 환경 레코드. 변수와 값을 의미
- Outer Environment Reference : 외부환경 참조. 위쪽 변수 또는 함수
- This binding : this 바인딩. 식별자가 바라봐야 할 대상 객체
Environment Record란 현재 컨텍스트와 관련된 식별자와 식별자에 바인딩된 값이 기록되는 공간이다. 더불어 실행 컨텍스트 내부 전체를 처음부터 끝까지 확인하며 순서대로 수집한다. 여기서 식별자는 변수 또는 함수의 이름을 말하며, 변수는 실제 객체(함수 객체 및 배열 객체 포함) 또는 원시값에 대한 참조이다.
환경 레코드 (Environment Record)
환경 레코드는 렉시컬 환경 내에 변수와 함수 선언이 저장되는 곳이다. 환경 레코드로 인하여 호이스팅이 발생한다.
환경 레코드는 두가지 타입이 있다.
- 렉시컬 환경 레코드 (Lexical Environment Record)
변수와 함수 선언을 저장하는 공간이다. 함수 코드를 위한 렉시컬 환경은 렉시컬 환경 레코드를 포함한다. - 객체 환경 레코드 (Object Environment Record)
전역 코드의 렉시컬 범위는 객체 환경 레코드를 생성한다. 변수, 함수 선언과 별개로 객체 환경 레코드는 전역 바인딩 객체(브라우저는 윈도우 객체)를 저장한다. 그래서 각 바인딩 객체의 프로퍼티에 대해 레코드에 새로운 항목이 생성된다.
함수 코드의 경우, 함수 환경 레코드에는 함수에 전달된 index~arguments 간의 매핑과 함수에 전달된 arguments의 length(number)가 포함된 arguments 객체도 포함되어 있다.
arguments 객체 예제 :
function foo(a, b) {
let c = a + b;
}
foo(2, 3);
// argument object
// Arguments: {0: 2, 1: 3, length: 2},
외부 환경 참조 (Outer Environment Reference)
외부 환경 참조는 외부 렉시컬 환경에 대한 접근을 의미한다. 자바스크립트 엔진이 현재 렉시컬 환경에서 변수들을 찾을 수 없다면, 부모의 렉시컬 환경에 접근하여 값을 찾을 수 있다는 뜻이다.
외부 환경 참조로 인하여 스코프와 스코프 체인이 형성된다.
This 바인딩 (This Binding)
컴포넌트에서 this의 값은 결정되거나 set 된 것이다.
전역 실행 컨텍스트에서 this의 값은 전역 객체를 참조한다. (브라우저의 경우에는 윈도우 객체 참조)
함수 실행 컨텍스트에서 this의 값은 함수가 어떻게 호출되는지에 달렸다. 만약 객체 참조로부터 호출되면 this의 값은 set되고 그렇지 않다면, 전역 객체 또는 undefined로 set 된다.
this 예제 :
const person = {
name: 'peter',
birthYear: 1994,
calcAge: function() {
console.log(2018 - this.birthYear);
}
}
person.calcAge();
// 'this' 는 'person'을 참조한다.
//왜냐하면, 'calcAge'는 'person' 객체 참조로 호출됐기 때문이다.
const calculateAge = person.calcAge;
calculateAge();
// 'this' 는 전역 윈도우 객체를 참조한다.
// 왜냐하면, 아무런 객체 참조도 주어지지 않았기 때문이다.
변수환경 (VariableEnvironment)
Variable Environment에 담기는 내용은 Lexical Environment와 같지만, 최초 실행 시의 스냅샷을 유지한다. 실행 컨텍스트를 생성할 때 Variable Environment에 먼저 정보를 담고, 이를 복사해서 LexicalEnvironment를 만든다.
변수 환경은 렉시컬 환경이기도 한데, 그래서 모든 프로퍼티와 위에 정의된 렉시컬 환경의 컴포넌트들을 가지고 있다.
ES6에서 렉시컬 환경 컴포넌트와 변수 환경 컴포넌트의 차이점
- 렉시컬 환경 컴포넌트 : 함수 선언과 변수(let, const) 바인딩을 저장하는데 사용한다.
- 변수 환경 컴포넌트 : 변수(var) 바인딩만 저장하는데 사용한다.
주로 활용하는 것은 Lexical Environment이다. 즉, Variable Enviroment는 스냅샷 유지를 목적으로 사용한다.
환경 레코드와 호이스팅 (Hoisting)
- 변수 선언 후 호출 예시 :
var a = "a";
let a1 = "a1";
const a2 = "a2";
console.log(a); // a
console.log(a1); // a1
console.log(a2); // a2
- 변수 선언 전 호출 예시 :
console.log(b); // undefined
console.log(c); // Reference error
console.log(d); // Reference error
var b = "b";
let c = "c";
const d = "d";
위 코드에서 변수가 선언되기 전에 호출하면 var 변수에는 undefinde로 액세스할 수 있지만 변수가 선언되기 전에 let 및 const 변수에 액세스할 때는 참조 에러가 발생한다.
이 때, 호이스팅이라는 개념이 이용된다.
자바스크립트 엔진은 전역 코드를 실행하기 위해 전역 실행 컨텍스트를 생성한다. 전역 코드를 실행하는 동안 변수 할당은 마무리 된다. 즉, 자바스크립트 엔진은 이미 실행 컨텍스트에 속한 변수명들을 모두 알고 있다는 뜻이다.
호이스팅이란 자바스크립트 엔진이 실행 컨텍스트를 구성할 때 환경 레코드에 식별자의 정보를 수집한다. 이러한 과정을 통해 엔진은 함수를 실행하기도 전에 해당 컨텍스트 내부의 변수명들을 이미 알고 있다.
그래서 식별자들을 코드의 최상단으로 끌어올리는 것으로 간주하자는 의미로 호이스팅이라는 개념이 생겨났다. 실제로 식별자들을 끌어올린 것이 아니라 실행 컨텍스트 관점에서는 이미 식별자들의 정보를 알고 있으니 식별자 정보를 식별자 정보를 수집하는 과정을 이해하기 쉬운 방법으로 나타낸 가상의 개념이다.
var와 let, const는 호이스팅과 선언의 개념이 조금 다르기 때문에 이에 관해서는 추후 포스팅할 예정이다.
외부 환경 참조와 스코프 (Scope)
Scope 란?
스코프란 변수에 접근할 수 있는 범위를 뜻한다. 자바스크립트에서 스코프는 2가지 타입이 있다.
- 전역 스코프 (Global Scope) : 어느 곳에서든 해당 변수에 접근 할 수 있다.
- 지역 스코프 (Local Scope) : 해당 지역에서만 접근할 수 있어서 지역을 벗어나면 접근할 수 없다.
코드 예시 :
let a = 10;
function fn() {
let b = 20;
console.log(b);
}
fn(); // 20
console.log(a); // 10
console.log(b) // ReferenceError: b is not defined
자바스크립트에서 함수를 선언하면 함수를 선언할 때마다 새로운 스코프를 생성한다. 그러므로 함수 내부에서 선언한 변수는 함수 내부에서만 접근할 수 있고, 이를 함수 스코프(Function Scope)라고 한다. 함수 스코프가 지역 스코프의 대표적인 예시이다.
스코프 체인 (Scope Chain)
- 식별자의 유효범위를 안에서 바깥으로 차례로 검색해나는 것을 의미한다.
- 이를 가능하게 하는 것이 외부 환경 참조 (Outer Environment Reference)이다.
코드 예시 :
let a = 'hello';
function outer () {
function inner () {
console.log(a); // undefined
let a = 'hi';
console.log(a); // hi
}
inner();
console.log(a); // hello
}
outer();
console.log(a); // hello
- inner ( )가 실행될 때는 outer( )의 렉시컬 환경에서 외부 환경 참조 (Outer Environment Reference)를 참조한다.
- outer( )가 실행될 때는 글로벌 실행 컨텍스트의 렉시컬 환경에서 외부 환경 참조 (Outer Environment Reference)를 참조한다.
위의 코드를 자세히 설명하자면, 외부 환경 참조는 현재 호출된 함수가 선언될 당시의 렉시컬 환경 (Lexical Environment)를 참조한다. 여기서 선언될 당시가 중요한데, outer( ) 함수가 선언될 당시의 외부 환경 참조 (Outer Environment Reference)는 글로벌 실행 컨텍스트의 Lexical Environment를 참조하고 있으며, 해당 환경의 Environment Record에 hello라는 식별자의 정보들이 기록되어 있다. 그래서 함수 내부에선 Outer Environment Reference를 통해 상위 컨텍스트의 Lexical Environment에 접근하여 Environment Record에서 변수인 hello를 사용할 수 있게 되는 것이다.
Outer Environment Reference는 오직 자신이 선언될 당시의 Lexical Environment를 참조하기 때문에 순차적으로만 접근이 가능하며, 여러 스코프에서 식별자를 생성하였다 하더라도 가장 먼저 발견된 식별자만 접근이 가능하다.
외부 환경 참조(Outer Environment Reference)란 해당 함수가 선언된 위치의 렉시컬 환경(Lexical Environment)를 참조한다. 변수에 접근하고자 할 때, 해당 Lexical Environment에서 발견된다면 사용하고, 찾지 못할 경우 다시 Outer Environment Reference를 참조하여 탐색하는 과정을 반복합니다. 이러한 과정을 스코프 체인(Scope Chain)이라고 하며 Outer Environment Reference는 스코프체인을 가능하게 하는 역할을 한다.
요약 및 결론
실행 컨텍스트(Execute Context)는 Variable Enviroment, Lexical Environment, Environment Record, Outer Environment Reference, This Binding 등 여러 정보들이 모여 Execute Context가 되고, 실행 컨텍스트가 Call Stack에 쌓여 코드가 실행된다.
자바스크립트에 대해 좀 더 제대로 이해해보고 싶어서 작성하게 된 포스팅이다. 포스팅을 작성하면서 시간이 꽤 오래 걸렸지만 자바스크립트 엔진이 어떻게 작동하는지, Execute Context와 Call Stack이 무엇인지 알게 되어서 유익한 시간이었다.
[참고자료]
https://youtu.be/pfQfEwnJHRs?si=tjFmkIMPQ3jnO3pB
자바스크립트의 실행 컨텍스트(Execute Context)와 실행 스택(Execute Stack) 이해하기
이 글은 Sukhjinder Arora의 Understanding Execution Context and Execution Stack in Javascript를 번역한 것입니다.
velog.io
JS Execution Context (실행 컨텍스트) 란? | 감구마 개발블로그
코어자바스크립트를 읽고 얻은 지식 중 실행 컨텍스트, 콜스택 | JS Execution Context, JS CallStack
gamguma.dev
자바스크립트 실행 컨텍스트 | 개발자 황준일
자바스크립트 실행 컨텍스트 실행 컨텍스트는 자바스크립트에서 가장 중요한 핵심 개념 중에 하나다. 이를 정확히 이해하는 것은 자바스크립트 개발자에게 매우 중요하다. 1. 개념 실행 컨텍스
junilhwang.github.io
'Front-End > JavaScript' 카테고리의 다른 글
[JavaScript] 콜백함수(Callback Function) (0) | 2023.09.08 |
---|---|
[JavaScript] Closure(클로저) (1) | 2023.09.05 |
[JavaScript] var, let, const 에 대해 알아보자 (0) | 2023.09.01 |