[Javascript] 실행 컨텍스트(Execution Context) 정리!!
실행 컨텍스트 정의
실행 가능한 코드를 형상화하고 구분하는 추상적인 개념
= 실행 가능한 코드가 실행되기 위해 필요한 환경
자바스크립트 엔진은 실행 가능한 코드를 실행하기 위해 필요한 정보를 형상화하고 구분하기 위해 실행 컨텍스트를 물리적 객체의 형태로 관리한다.
실행 가능한 코드
- 전역 코드 : 전역 영역에 존재하는 코드
- 함수 코드 : 함수 내에 존재하는 코드
- Eval 코드 : eval 함수로 실행되는 코드
Eval 함수? 문자로 표현 된 JavaScript 코드를 실행하는 함수. 인자로 받은 코드를 caller의 권한으로 수행하므로, 제 3자 코드가 eval()이 호출된 위치의 스코프를 볼 수 있으며, 비슷한 함수인 Function으로는 실현할 수 없는 공격이 가능하여 절대 쓰지 않는 것이 좋다. |
실행에 필요한 정보
- 변수 : 전역변수, 지역변수, 매개변수, 객체의 프로퍼티
- 함수 선언
- 변수의 유효범위(Scope)
- this
정리하면
- 변수, 함수 선언, 스코프, this는 ****전역, 함수 코드를 실행하기 위해 필요한 정보이다.
- 실행 컨텍스트는 전역 코드, 함수 코드를 실행하기 위한 환경이다.
실행 컨텍스트 스택
앞으로 실행 가능한 코드를 편의상 '함수'라고 하겠다.
함수를 실행하면, 실행 컨텍스트가 생성된다.
그 안에서 또 다른 함수가 실행되면 그 위에 실행 컨텍스트가 생성되어, 스택 구조로 콜스택 메모리에 쌓인다.
그리고 함수 실행이 종료되면, 실행 컨텍스트가 파기되는 LIFO 구조다.
var x = 'xxx';
function foo () {
var y = 'yyy';
function bar () {
var z = 'zzz';
console.log(x + y + z);
}
bar();
}
foo();
위 코드의 실행 컨텍스트 스택은 아래 그림과 같다.
- 전역 컨텍스트(Global EC)로 컨트롤 이동 (전역 컨텍스트는 그냥 가장 먼저 실행되는 컨텍스트라고 이해하면 된다)
- foo() 함수 실행으로 foo() 실행 컨텍스트 스택이 생성 → foo() 실행 컨텍스트로 컨트롤 이동
- foo() 함수 내에서 bar() 함수 실행으로 bar() 실행 컨택스트 스택이 생성 → bar() 실행 컨텍스트로 컨트롤 이동
- bar() 함수 실행 종료 → bar() 실행 컨텍스트 파기 → 이전 컨텍스트인 foo()로 컨트롤 이동
- foo() 함수 실행 종료 → foo() 실행 컨텍스트 파기 → 이전 컨텍스트인 전역 컨텍스트로 컨트롤 이동
실행 컨텍스트의 3가지 프로퍼티
실행 컨텍스트는 물리적으로는 객체의 형태를 가지며 아래의 3가지 프로퍼티를 소유한다.
- 활성 객체(변수 객체, Variable Object, VO)
- 스코프 체인(Scope Chain)
- this
실행 컨텍스트 생성 및 실행 과정
- 활성 객체(변수 객체) 생성
- arguments 객체 생성
- 스코프 정보 생성
- 변수 생성
- this 바인딩
- 코드 실행
function execute(param1, param2) {
var a = 1, b = 2;
function func() {
return a + b;
};
return param1 + param2 + func();
};
execute(3, 4);
활성 객체(변수 객체, Variable Object, VO) 생성
활성 객체, 즉 변수 객체는 실행에 필요한 어려가지 정보를 담을 객체이다. 이 객체에는 변수, 매개변수(parameter), 전달인자(arguments), 함수 선언(함수 표현식은 제외)이 저장된다.
arguments 객체 생성
자바스크립트에서는 함수를 호출할 때 암묵적으로 arguments 객체가 함수 내부로 전달된다. 넘긴 인자들이 배열 형태로 저장된 객체이다.
특이한 점은, arguments 객체는 배열이 아니라 유사 배열 객체이다. 유사배열 객체는 객체임에도 불구하고, apply() 를 통해서 자바스크립트의 표준 배열 메소드를 사용하는게 가능하다. 그러나 배열 메소드를 바로 사용하는 것은 불가능하다.
arguments 객체는 다음과 같이 세 부분으로 구성되어 있다.
- 함수 호출 시 넘겨진 인자 (배열 형태)
- length 프로퍼티
- callee 프로퍼티: 현재 실행중인 함수의 참조값
스코프 정보 생성
스코프 정보는 현재 컨텍스트의 유효 범위를 나타낸다. 스코프 정보는 현재 실행 중인 실행 컨텍스트 안에서 linked list와 유사한 형식으로 만들어진다.
이 리스트를 '스코프 체인'이라고 한다.
스코프 체인
[[scope]] 프로퍼티로 참조되는 스코프 정보들의 linked list이다.
스코프 체인은 현재 컨텍스트의 변수 뿐만 아니라, 상위 실행 컨텍스트의 변수도 접근이 가능하다. 이 리스트에서 찾지 못한 변수는 결국 정의되지 않은 변수에 접근하는 것으로 판단하여, 에러를 검출한다.
현재 생성된 활성 객체(변수 객체)가 스코프 체인의 가장 앞에 추가된다.
변수 생성
현재 실행 컨텍스트 내부에서 사용되는 지역 변수들이 생성된다.
- 호출된 함수 인자, param1, param2 프로퍼티가 만들어지고 그 값이 할당된다. 만약 값이 넘겨지지 않았다면, undefined 가 할당된다.
- 함수 내부에 정의된 변수 a, b와, 함수 func가 생성된다. 이 과정에서는 변수나 내부 함수를 단지 메모리에 생성할 뿐이다. 즉, 변수 a, b에는 각각 undefined 가 할당된다. 그렇다면 초기화는 언제 이루어질까. 각 변수나 함수에 해당하는 표현식이 실행되면 초기화가 이뤄진다.
왜 함수는 초기화까지 이뤄진거죠? 이는 func 함수가 함수 선언식으로 작성되었기 때문에 그렇다. 함수 표현식은 변수 객체가 모두 만들어진 이후에 코드가 실행되는 시점에 읽힌다. 그러나 함수 선언식은 초기화 단계에서 읽힌다. |
this 바인딩
this 키워드를 사용하는 값이 할당된다. 여기서 this가 참조하는 객체가 없으면 전역 객체를 참조한다.
코드 실행
하나의 실행 컨텍스트가 생성되고, 변수 객체가 만들어진 후에 드디어 코드에 있는 여러 표현식의 실행이 이루어진다.
이 과정에서 변수의 초기화 및 연산, 또 다른 함수 실행 등이 이뤄지는 것이다. 예제 코드의 변수 a, b 에 각각 1, 2 값이 할당된다.
참고로, 전역 실행 컨텍스트는 일반적인 실행 컨텍스트와는 약간 다르다. arguments 객체가 없으며, 전역 객체 하나만을 포함하는 스코프 체인이 있다. 또한 전역 실행 컨텍스트에서는 변수 객체가 곧 전역 객체이다. 따라서, 전역적으로 선언된 함수와 변수가 전역 객체의 프로퍼티가 된다.