실행 컨텍스트
시작
이어서 실행컨텍스트에 대해 알아보겠습니다.
실행 컨텍스트
실행컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다.
자바스크립트는 어떤 실행컨텍스트가 활성화 되는 시점에 선언된 변수를 위로 끌어올린 후(호이스팅) 외부 환경 정보를 구성하고, this 값을 설정합니다.
이런 현상들이 JS의 특징입니다.
콜 스택
우선 콜 스택을 알기 위해 스택
과 큐
에 대해 알아야하죠. 스택은 LIFO
, 큐는 FIFO
입니다. 이중 저희는 스택에 대해 집중할겁니다.
왜나하면 실행 컨텍스트란 실행할 코드에 제공할 환경 정보들을 스택으로 쌓기 때문입니다.
컨텍스트를 구성하는 방법
- 전역공간 : 자바스크립트 파일이 실행되는 순간 자동 부여되는 전역 컨텍스트
- eval() 함수 : 추후 알아보고 링크 첨부 예정
- 함수 : 흔히 실행컨텍스트를 구성하는 방법. 지금 글에서 가장 주요하게 다룰 구분 방법
다음은 실행컨텍스트 구성 예시이다.
// 1. 전역 컨텍스트 생성
var a = 1;
function outer(){
function inner(){
console.log(a);
var a = 3;
}
inner(); // 2번 실행
console.log(a)
}
outer(); // 3 함수 선언이 끝난 시점에서 outer함수 실행 즉, 실행 컨텍스트가 생성된다.
console.log(a)
위 코드는 다음과 같은 순서로 실행된다. 이 자바스크립트가 생성되는 시점을 순간으로 다음과 같은 순서로 실행된다.
코드실행 -> 전역 컨텍스트 in -> 전역 컨텍스트 stop + outer() in -> outer() stop + inner() in -> inner() out + outer run -> outer() out + 전역 컨텍스트 run -> 전역 컨텍스트 out -> 코드 종료
[그림1 첨부 예정]
실행 컨텍스트 객체
하나의 실행 컨텍스트가 콜 스택 맨위 쌓이는 순간은 생성(활성화)시점이다. 즉, 활성화 시점에 엔진은 해당 컨텍스트에 관련 코드를 실행하는데 필요한 환경 정보를 수집해 실행 컨텍스트 객체에 저장한다.
이 실행 컨텍스트에 담기는 정보를 살펴보도록 하자.
VariableEnvironment
현재 컨텍스트 내 식별자 정보(변수의 식별자 정보들.), 외부 환경 정보, 선언 시점의 LexicalEnviroment snapshot 즉, 선언 시점의 환경을 기억
LexicalEnviroment
VariableEnvironment와 동일하지만 변경사항이 실시간으로 반영.
ThisBinding
this 식별자가 바라봐야할 객체
VariableEnvironment와 LexicalEnviroment
축약해서 VE, LE로 설명하겠습니다. 담기는 내용은 동일하나, VE는 스탭샷을 유지합니다. 실행 컨텍스트를 생성하는 경우 VE에 정보를 먼저 담고 이를 그대로 복사해서 LE를 만듭니다. 이후 주로 LE를 이용하죠.
구성요소는 다음과 같습니다.
- VE, LE 모두 동일하며
environmentRecord
와outerEnvironmentReference
로 구성됩니다. - environmentRecord에는 현재 컨텍스트 관련 코드 및 식별자 정보들이 저장됩니다. 함수에 지정된 매겨변수 식별자, 함수 자체, 키워드(var, let, const)로 선언된 변수 식별자 등
- outerEnvironmentReference는 현재로서는 외부 컨텍스트로 보면 됩니다.
LexicalEnviroment(1) environmentRecord와 호이스팅
environmentRecord는 현재 컨텍스트와 관련 코드 및 식별자 정보들이 저장된다고 말씀드렸죠? 수집 대상도 말씀드렸습니다. 즉, 컨텍스트 내부를 처음부터 끝까지 순서대로 흝어가며 수집하게 됩니다.
호이스팅
변수정보 수집을 모두 마쳤더라도 아직 실행 컨텍스트가 관리할 코드는 실행 전 상태입니다. 엔진은 이 단계에서 일단 변수정보는 알게됩니다. 즉 일종의 가상 개념이라고 보면 됩니다.
다음 코드는 어떻게 동작할까요? 우선 전역 컨텍스트를 쌓고 a함수를 쌓습니다.
function a(x){
console.log(x); // 1
var x;
console.log(x); // undefined(x) 1(o)
var x = 2;
console.log(x) // 2
}
a(1);
실행 결과를 예상했을때 우선 1이 출력되고 undefined가 출력되리라 생각하셨을겁니다. 하지만 결과는 1입니다. 왜그럴까요? 호이스팅에 의해 그렇습니다.
우선 인자로 넘어온 매개변수는 지역변수입니다. var x = 1;
을 알리겠죠? 이후에는 var x; var x =2;
를 호이스팅 할 겁니다. 그럼 아래와 같이 해석할 수 있겠군요.
function a() {
var x;
var x;
var x;
x = 1;
console.log(x); // 1
console.log(x); // 1
x = 2;
console.log(x); // 2
}
따라서 결과는 1 1 2 입니다.
이제 수집 대상 중 함수를 보도록 합시다. 함수 선언문 자체는 호이스팅 된다고 했습니다. 그럼 함수 표현식은 어떨까요? 함수 표현식의 경우 해당 변수만 호이스팅됩니다.
// 함수 선언문
function sum(a, b) {
}
// 함수 표현식
var multply = function(a, b){
}
함수 선언문을 사용할 때 주의점이 있습니다. 다음과 같이 선언문 명이 같은 경우입니다. 이후 중복 선언문이 앞의 선언문을 덮어 씌어버리는 겁니다.
console.log(sum(3,4)) // 3 + 4 = 5
function sum(x, y) {
return x + y;
}
function sum(x, y) {
return x + '+' + y + '=' + (x + y);
}
만약 함수 표현식이었다면 적어도 선언문 자체는 호이스팅하지 않으므로 위와 같은 상황은 면할 수 있겠죠.
LexicalEnviroment(2) 스코프와 스코프 체인 그리고 outerEnvironmentReference
스코프는 뭐죠? 식별자에 대한 참조 유효범위입니다. 스코프 체인은 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것입니다. outerEnvironmentReference
는 바로 스코프 체인이 가능하도록 외부 환경을 참조할 환경을 만들어주는 것이라고 보면 됩니다.
더 자세히 봅시다. 스코프 체인에 대해 보도록 하죠.
- outer는 현재 호출된 함수가 선언될 당시 렉시컬 환경을 참조합니다. ex) A함수 내부에 B함수 선언 -> B함수 내부에 C함수 선언, A함수는 B를 담고 B는 C를 담죠. 일종의 LinkedList와 같습니다.
- 결국 전역 컨텍스트의 렉시컬 환경(내부 환경)을 참조합니다.
- 오직 자신이 선언된 시점의 렉시컬 환경을 참조하고 있으므로 가장 가까운 요소부터 차례대로 접근합니다.
결론을 보자면 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능하다고 보시면됩니다.
var a = 1;
var outer = function() {
var inner = function() {
console.log(a); // undefined
var a = 3;
// var a;
// console.log(a);
// a = 3;
};
inner();
// inner() 컨텍스트가 끝난 시점
console.log(a); // 스코프에서 현재 a변수를 찾을 수 없다. 외부를 참조한다. a즉 1이 출력된다.
};
outer();
// outer() 컨텍스트가 끝난 시점
console.log(a) // 현재 스코프에 a가 존재 1
우선 콜 스택에는 전역 컨텍스트, outer(), inner()로 쌓이고 반대로 실행이 되면서 점차 out될겁니다.
전역 컨텍스트를 제외한 컨텍스트들은 recode와 outer를 담고 있습니다. recode에 없다면 outer를 참조하죠 즉 외부 함수를 참조합니다. 끝내 없다면 전역 컨텍스트를 참조하는 거죠