실행컨텍스트(Execution Context)는 코드가 실행되는 환경을 말한다.실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요하나 환경을 제공하는 추상적인 개념으로 함수 실행, 전역 실행 등과 같은 실행 환경을 나타내는데 사용된다. 실행 컨텍스는 스택(Stack)으로 구성되어 있으며, 실행 순서는 스택의 맨 위에서부터 시작된다.
자바스크립트에서는 실행 컨텍스트가 세 가지 종류로 구분된다.
전역 실행 컨텍스트(Global Execution Context, GEC): 자바스크립트에서 실행되는 모든 코드는 전역 실행 컨텍스트 내에서 실행된다. 이 실행 컨텍스트의 주요 기능은 전역 객체(Global Object)
를 생성하고 this 값을 전역 객체에 할당한다.
함수 실행 컨텍스트(Function Execution Context, FEC): 함수가 호출될 때마다 생성되는 실행 컨텍스트이다. 함수가 선언될 떄는 실행 컨텍스트가 생성되지 않으며, 함수가 호출될 때 실행 컨텍스트가 생성된다. 함수가 호출될 때마다 해당 함수의 FEC가 실행스택에 추가된다. 함수가 실행을 완료하면 해당 FEC가 스택에서 제거된다.
eval()실행 컨텍스트: val() 함수의 성격상 보안에 취약하기 때문에 자주 사용되지 않는다. 그러나 eval() 함수가 사용될 때마다 실행 콘텍스트가 생성된다.
💡 콜 스택과 실행 컨텍스트의 개념을 확실히 하기 위해서 ChatGPT에게 얻은 답변💡
✓ 요약: 실행컨텍스트는 객체이며 함수가 호출될 때마다 변수, 함수, 매개변수 등의 정보를 담고 콜스택에 쌓이게 된다. 이 쌓인 콜스택은 메모리에 할당된다.
ChatGPT: 네, 맞습니다. 콜 스택 안에는 실행 컨텍스트 객체가 쌓이며, 이 콜 스택은 메모리에 할당됩니다.
실행 컨텍스트는 함수 호출이 발생할 때 생성되고, 함수의 실행이 완료되면 콜 스택에서 제거됩니다. 따라서 함수가 호출될 때마다 해당 함수의 실행 컨텍스트가 콜 스택에 쌓이게 됩니다.
각 실행 컨텍스트 객체는 변수, 함수, 매개변수 등의 정보를 담고 있으며, 실행 중인 코드 블록의 환경을 구성합니다. 이 실행 컨텍스트 객체가 콜 스택에 쌓이는 과정은 메모리에 해당 객체를 할당하는 것과 관련이 있습니다. 실행 컨텍스트 객체는 JavaScript 엔진이 메모리에 할당하여 관리하는 객체입니다.
따라서 콜 스택은 메모리에 할당되는 자료 구조로, 그 안에는 실행 컨텍스트 객체들이 쌓이는 형태로 동작합니다. 콜 스택은 함수 호출과 반환의 순서를 관리하고, 실행 컨텍스트 객체들은 각각의 함수 실행에 필요한 정보를 유지하면서 JavaScript 코드의 실행을 지원합니다.
실행컨텍스트를 이해하기 위해서는 먼저 선행되어야 할 개념이 있다. 그 중 하나가 자바스크립트의 '호이스팅'이다.
👆🏻 호이스팅이란 변수와 함수 선언이 스코프의 최상단으로 올려진 듯한 현상을 말한다. 실제로 스코프의 최상단에 올려진 것이 아니고 자바스크립트 엔진이 코드를 전체적으로 읽고 변수 같은 정보를 실행컨텍스트 정보를 미리 환경 레코드(Environment Record)
에 기록하기 때문이다.
-- 코드 예시 --
console.log(status); // undefined
var status = "Studying";
console.log(status); // Studying
자바스크립트에서 위의 코드를 실행하면 전역 실행 컨텍스트 한 칸을 생성해서 콜 스택에 넣는다. 그 후 코드를 읽으며 미리 선언할 식별자를 찾아보고 있다면 생성된 실행 컨텍스트 안의 환경 레코드(Environment ㅅRecord)에 식별자를 기록해 둔다. 아래의 코드의 경우 var 키워드로 선언했기 때문에 초기값을 { status: undefined }로 초기화 해둔다. 이러한 단계를 생성 단계(Creation Phase)
라고 한다.
생성 단계 이후 선언문 외에 나머지 코드를 순차적으로 실행하는 단계이다. 아래의 코드에서는 첫 줄의 status의 값을 출력하는 console.log(status)가 먼저 실행된다. 자바스크립트 엔진은 이를 출력하기 위해 활성화된 실행컨텍스트 내에 환경레코드를 보고 이미 기록된 status 값을 참조해서 출력한다. --> console.log(status); // undefined
<-- 그리고 다음 라인을 실행한다. 생성단계에서 선언을 미리 해놨으니 바인딩 된 값(현재는 undefined)을 "Studying"으로 할당만 실행해준다. {status : "Studying"}
이후 마지막 라인을 실행하면 자바스크립트 엔진은 환경 레코드를 참조해서 Status의 값을 Studying으로 결정한다.
console.log(status); // undefined
var status = "Studying";
console.log(status); // Studying
선언과 동시에 초기화가 이루어지는 var대신 const키워드로 변수를 선언한다면 엔진이 식별자를 기록하긴 하지만 값을 초기화 시켜두진 않는다. 따라서 선언문 이전에 값을 참조하려고 하면 " ReferenceError: Cannot access 'status' before initialization"와 같은 Reference Error가 발생한다.
이 처럼 let이나 const와 같은 변수로 선언한 경우 선언 이전에 식별자를 참조할 수 없는 구역을 TDZ(Temporal Dead Zone), 일시적 사각지대 라고 표현한다.
이미지 출처: https://ui.toast.com/weekly-pick/ko_20191014
그렇다면 함수의 경우 어떻게 호이스팅이 이루어질까?
함수 선언문(function declaration)
은 마찬가지로 해당 스코프의 최상단으로 끌어올려진다.자바스크립트 엔진이 study함수의 선언과 동시에 환경레코드에 {sayHello: f {}} 함수 객체를 생성해서 기록해 둔다. 함수 선언문의 경우 선언과 동시에 함수가 생성되는 것이다. 따라서 함수 선언문을 선언하기 이전에 호출해도 정상적으로 동작한다.
참고로 이 함수 선언문 방식은 함수 선언 이전에 호출할 수 있기 때문에 코드의 가독성과 스코프 규칙 등의 문제로 사용을 지양하는 경향이 있다.
sayHello(); // "Hello"
function sayHello() {
console.log("Hello");
}
함수 표현식(function expression)
은 선언문 이전에 실행하려고 하면 실행되지 않는다. 환경레코드에 기록되어 있는 sayHello의 값은 undefined이고 이 undefined 데이터 타입은 함수와 달리 호출될 수 없기 때문에 타입에러가 발생한다. 따라서 변수에 할당된 함수 표현식은 해당 변수가 선언된 위치부터 사용 가능하다. 같은 함수를 const에 선언하면 아직 환경레코드에 기록된 값이 없어 Reference Error가 발생한다. 이는 함수를 '변수'에 담고 있기 때문에 앞서 살펴본 변수 호이스팅과 같은 원리로 동작하는 것이다.
sayHello(); // TypeError: sayHello is not a function
var sayHello = function() {
console.log("Hello");
};
실행 컨텍스트는 렉시컬 환경(Lexical Environment)과 함께 동작한다. 이 때, Outer Outer Environment Reference
라는 개념이 등장하는데 간단하게 말하면 이는 현재 실행 컨텍스트의 외부 스코프(outer scope)에 대한 참조를 가리키는 용어이다. Outer Outer Environment Reference
는 스코프 체인
이라는 것을 통해 변수 검색을 수행한다.
변수 섀도잉은 동일한 이름의 변수가 중첩된 스코프에서 선언될 때 발생하는 현상이다. 내부 스코프에서 선언된 변수는 외부 스코프의 동일한 이름의 변수를 가리키게 되어 외부 스코프의 변수에 접근할 수 없다. 즉 즉 동일한 이름의 변수로 인해 상위 스코프에서 선언된 식별자의 값이 가려지는 현상이다. 이러한 일련의 과정에서 Outer Environment Reference를 통해 스코프 체인이 형성되어 변수를 검색하는데 이를 스코프 체인(Scope Chain)이라고 한다. 각각의 개념을 아래 코드를 통해 더 자세히 알아보자.
아래 코드에서 hello 함수 내부에서 var x = 20;으로 변수 x를 선언했다. 이로 인해 내부 스코프에서의 x는 외부 스코프의 x를 가리키게 된다. hello 함수 내에서 console.log(x);를 실행하면 내부 스코프의 x 값인 20이 출력됩니다. 하지만 외부 스코프에서 console.log(x);를 실행하면 외부 스코프의 x 값인 10이 출력된다.
var x = 10;
function hello() {
var x = 20;
console.log(x); // 내부 스코프의 x 출력: 20
}
hello();
console.log(x); // 외부 스코프의 x 출력: 10
아래 코드에서는 outer 함수 내부에서 var y = 20;으로 변수 y를 선언하였고, inner 함수 내부에서 var z = 30;으로 변수 z를 선언했다.
console.log(x + y + z);에서 변수 x, y, z에 접근하여 계산을 수행하는데, 이때 스코프 체인을 통해 변수에 접근한다.
inner 함수 내부에서 z 변수를 찾을 때, 현재 스코프에 z 변수가 선언되어 있기 때문에 바로 해당 값을 사용할 수 있다.
inner 함수 내부에서 y 변수를 찾을 때에는 현재 스코프에 y 변수가 없기 때문에 외부 스코프인 outer 함수의 스코프로 이동하여 y 변수를 검색한다. 이때 스코프 체인을 통해 y 변수에 접근할 수 있고, y 변수의 값인 20을 한다.
마지막으로 inner 함수 내부에서 x 변수를 찾을 때, 현재 스코프와 외부 스코프에 x 변수가 없기 때문에 전역 스코프로 이동하여 x 변수를 검색한다(전역 실행 컨텍스트는 전역 스코프와 관련된 변수와 함수를 초기화 해 놓는다. 따라서 전역 스코프는 더 참조할 바깥이 없는 최상위 스코프이다). 스코프 체인을 통해 x 변수에 접근할 수 있고, x 변수의 값인 10을 사용한다.
따라서 console.log(x + y + z)의 결과는 60이 된다. 이와 같은 스코프 체인을 통해 다른 스코프의 변수에 접근할 수 있음을 알 수 있다.
var x = 10;
function outer() {
var y = 20;
function inner() {
var z = 30;
console.log(x + y + z); // 스코프 체인을 통해 x, y, z 변수에 접근하여 계산
}
inner();
}
outer(); // 결과: 60
💡 스코프와 실행 컨텍스트의 개념이 헷갈려 ChatGPT에게 물어보았다💡
ChatGPT: 스코프는 변수와 함수에 대한 유효 범위를 말합니다. 스코프는 변수의 접근성과 식별자(identifier)의 해결을 결정합니다. 변수와 함수는 스코프에 의해 참조될 수 있으며, 스코프는 렉시컬 환경(lexical environment)으로 표현될 수 있습니다.
반면에 실행 컨텍스트는 코드 실행을 위해 필요한 환경 정보를 포함하고 있는 객체입니다. 실행 컨텍스트는 변수, 함수 선언, 매개변수 등의 정보를 담고 있으며, 코드 실행에 필요한 렉시컬 환경과 호이스팅 등의 동작을 수행합니다. 실행 컨텍스트는 함수 호출 시마다 생성되며, 스택(Stack)에 쌓여 순차적으로 실행됩니다.
실행 컨텍스트는 실행 가능한 코드(전역 코드, 함수 코드)가 실행되기 전에 생성되고, 코드 실행이 끝나면 스택에서 제거됩니다. 실행 컨텍스트는 현재 실행 중인 코드 블록의 환경 정보를 유지하고, 변수 및 함수 식별자의 해결을 위해 스코프 체인을 구성합니다.
따라서 스코프와 실행 컨텍스트는 서로 다른 개념이지만, 실행 컨텍스트는 스코프를 관리하고 스코프에 따라 변수와 함수에 접근하는 역할을 수행합니다. 때문에 두 용어가 혼동되어 사용되기도 합니다.
종합적으로 실행 컨텍스트는 JavaScript 코드에서 변수와 함수 식별자를 관리하기 위한 수단이다. 이를 통해 코드의 실행 결과를 도출할 수 있고 식별자 결정을 더 효율적으로 할 수 있게 되는 것이다.
어느 정도 실행 컨텍스트에 대한 윤곽이 머릿속에 잡힌 듯하다. [10분 테코톡] 영상의 발표 내용이 해당 개념을 이해하는 데 정말 도움이 많이 됐다. 영상을 전체적으로 본 이후 다른 자료들을 참고하니 보다 잘 이해가 되었다. 실행컨텍스트에 대해 공부했으니 다음 포스팅에서는 클로저 개념에 대해 공부한 후 포스팅 해야겠다~~!!