02. 실행 컨텍스트
⚠️ 실행 컨텍스트를 정확히 이해하자.
01. 실행 컨텍스트란?
- 의미: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체.
- JS의 동작 [1] 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올림(hoisting)
[2] 외부 환경 정보를 구성
[3] this 값을 설정
- 역할: 전체 코드의 환경과 순서를 보장 동일한 환경에 있는 코드들을 실행할 때 필요 환경 정보들을 모아 컨텍스트를 구성, 콜 스택 call stack에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스와 관련 있는 코드들을 실행하는 식 *동일한 환경**: 하나의 실행 컨텍스트를 구성할 수 있는 방법 - 전역공간, eval 함수, 함수
- 실행 컨텍스트 객체: JS 엔진이 어떤 실행 컨텍스트가 활성화될 때 그 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집
- VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷 snapshot, 변경 사항은 반영되지 않음
- LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영
- ThisBinding: this 식별자가 바라봐야 할 대상 객체
02. VariableEnvironment
- 차이: LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다름.
- 00실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담고, LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용.
- 내부 구성(공통): VariableEnvironment와 LexicalEnvironment 둘 모두 내부 구성은 아래와 같이 동일하다.
- environmentRecord
- outer-EnvironmentReference
03. LexicalEnvironment
2-3-1 environmentRecord와 호이스팅
- environmentRecord: 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장.
- 식별자의 종류 [1] 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자
[2] 선언한 함수 자체
[3] var로 선언된 변수의 식별자
- 참고 전역 실행 컨텍스트는
변수 객체가 아닌, JS 구동 환경이 별도로 제공하는 전역 객체를 활용 *전역 객체**: 브라우저의 window, Node.js의 global 객체 등이 있음
이들은 JS 내장 객체(native object)가 아닌 호스트 객체(host object)로 분류됨
- hoisting: 컨텍스트 내부 전체를 처음부터 끝까지 훑어 나가며 순서대로 수집
- BUT 코드 실행 전에 수집
- 실제로 끌어올리지는 않지만, 편의상 JS 엔진은 식별자들을 최상단으로 끌어올린 후 실제 코드를 실행한다고 생각 가능
hoisting 규칙
- 변수명과 함수 선언의 정보를 수집
- 변수: 선언부와 할당부를 나누어 선언부만 수집
- 함수 선언: 함수 전체를 수집
- 함수 표현식: 변수 선언부만 호이스팅
- hoisting 규칙에 의해 전역에서 호출 가능한
함수 선언보다는 선언 이후 실행 가능한 함수 표현식 사용하는 게 best!
- EX) 전역공간에 동명의 함수가 여럿 존재하는 상황에서의 디버깅!
@매개변수와 변수에 대한 호이스팅
function a (x) {
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1);
⚠️ 주의: 실제 JS 엔진은 이러한 변환 과정을 거치지 않음. 사람 입장에서의 코드로 변환한 것!
function a (){
var x;
~~var x;
var x;
x = 1;
console.log(x);
console.log(x);
x = 2;
console.log(x);
}
a(1);
실제 코드 실행
[1] 변수 x 선언(메모리 공간 미리 확보, 주솟값 변수 x에 연결)
[2] 다시 변수 x 선언, 이미 선언된 변수 x가 있으므로 무시
[3] x에 1 할당
[4] 각 x 출력, 모두 1 출력됨
[5] x에 2 할당(원래 1을 가리키는 주솟값 → 2의 주솟값으로 대치)
[6] x 출력, 2 출력됨
[7] 함수 내부의 모든 코드가 실행됐으므로 실행 컨텍스트가 콜 스택에서 제거됨
@함수 선언의 호이스팅
function a () {
console.log(b);
var b = 'bb';
console.log(b);
function b(){}
console.log(b);
}
a();
function a(){
var b;
function b (){}
console.log(b);
b = "bbb";
console.log(b);
console.log(b);
}
a();
function a(){
var b;
var b = function b (){};
console.log(b);
b = "bbb";
console.log(b);
console.log(b);
}
a();
실행 시
[1] 변수 b 선언
[2] 함수는 별도의 메모리, 그 함수가 저장된 주솟값을 b와 연결된 공간에 저장. 이제 변수 b는 함수를 가리킴
[3] 변수 b에 할당된 함수 b가 출력
[4] 변수 b에 “bbb”를 할당
[5] “bbb” 출력 후 실행 컨텍스트는 콜 스택에서 제거(모든 코드 실행됐으므로)
함수 선언문과 함수 표현식
@함수 선언문과 함수 표현식
console.log(sum(1,2));
console.log(multipy(3,4));
function sum(a,b){
return a+b;
}
var multiply = function(a,b){
return a*b;
}
var sum = function sum(a,b){
return a+b;
}
var multiply;
console.log(sum(1,2));
console.log(multipy(3,4));
multifly = function(a,b){
return a*b;
}
실행 시
[1] sum ← 메모리 공간 확보 후 연결
[2] multiply ← 메모리 공간 확보 후 연결
[3] sum 함수 → 또 다른 메모리 공간에 저장, 그 주솟값을 변수 sum의 공간에 할당.
[4] sum 실행
[5] multiply is not a function 이라는 에러 메시지가 출력됨
← 현재 multiply에는 값이 할당되어 있지 않음. 비어있는 대상을 함수로 여겨 출력하라는 명령이었음.
2-3-2 스코프, 스코프 체인, outerEnvironmentReference
- scope: 식별자에 대한 유효범위
- ES5까지의 JS는 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성
- 스코프 체인 scope chain: 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것
- LexicalEnvironment의 outerEnvironmentReference 활용
스코프 체인
- outerEnvironmentReference: 현재 호출된 함수가 선언될 당시*의 LexicalEnvironment를 참조 선언될 당시* : A 함수 내부에 B 함수를 선언, B 함수 내부에 C 함수를 선언
함수 C의 outerEnvironmentReference → 함수 B의 LexicalEnvironment를 참조
(함수 C가 선언될 당시)
함수 B의 outerEnvironmentReference → 함수 A의 LexicalEnvironment를 참조
(함수 B가 선언될 당시)
- outerEnvironmentReference는 연결 리스트의 형태
- 선언 시점의 LexicalEnv를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnv가 있을 것
- 여러 스코프에서 동일한 식별자를 선언한 경우 - 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근이 가능
@예제
var a = 1;
var outer = function() {
var inner = function () {
console.log(a);
var a = 3;
};
inner();
console.log(a);
};
outer();
console.log(a);
- 설명 [1] 가장 처음
전역 컨텍스트 활성화
전역 컨텍스트 → environmentRecord = {a, outer} 식별자를 저장
전역 컨텍스트 → outerEnv-Ref = 아무것도 안 담김 [2] 전역 스코프에서 호이스팅
전역 스코프에 있는 변수 a ← 1,
outer ← 함수 할당 [3] outer();
outer 함수 호출 전역 컨텍스트의 코드는 임시중단,
outer 실행 컨텍스트가 활성화,
2번째 줄로 이동 [4] var outer = function () {
outer 실행 컨텍스트 → environmentRecord = {inner} 식별자 저장
outer 실행 컨텍스트 → outerEnv-Ref = [GLOBAL, {a, outer}][GLOBAL, {a, outer}]* : 전역 컨텍스트 → LexicalEnvironment를 참조복사한 것을 편의상 표기.
1번째 인자 = 실행 컨텍스트의 이름, 2번째 인자 = environmentRecord 객체 [5] var inner = function () {
outer 스코프에 있는 변수 inner에 함수 할당 [6] inner();
inner 함수 호출. outer 실행 컨텍스트 임시중단, inner 실행 컨텍스트 활성화 [7] var inner = function () {
inner 실행 컨텍스트 → environmentRecord = {a} 식별자 저장
inner 실행 컨텍스트 → outerEnv-Ref = [outer, {inner}] [8] console.log(a);
식별자 a에 접근, inner 컨텍스트의 envRec에서 a를 검색
undefined 출력 [9] var a = 3;
inner 스코프에 변수 a에 3 할당 [10] };
inner 함수 실행이 종료, inner 실행 컨텍스트가 콜 스택에서 제거, outer 실행 컨텍스트가 다시 활성화 [11] console.log(a);
식별자 a 에 접근.
활성화된 실행 컨텍스트의 LexicalEnv에 접근, 매 요소마다 envRec에 a 있는 지 찾아보고, 없으면 outerEnvRef에서 environmentRecord에서 a 찾는 식으로 계속해서 검색 [12] };
outer 함수 실행 종료
outer 실행 컨텍스트가 콜 스택에서 제거, 바로 아래의 전역 컨텍스트가 다시 활성화 [13] console.log(a);
식별자 a에 접근
현 활성화된 전역 컨텍스트의 environmentRecord에서 a를 검색
전역 컨텍스트가 콜 스택에서 제거되고 종료
@ 간략 요약 표
- LE = LexicalEnvironment, e = environmentRecord, o = outerEnvironmentReference
- 변수 은닉화
- 스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 것은 아니며, 가장 가까운 실행 컨텍스트에서 어떤 변수가 이미 검색되는 경우, 스코프 체인 검색을 더 이상 진행하지 않음. 즉, 접근할 수 없는 것과 마찬가지.
전역변수와 지역변수
- 전역변수: 전역 공간에서 선언한 변수
- 지역변수: 함수 내부에서 선언한 변수
- 코드의 안전성을 위해 가급적 전역변수 사용을 최소화하고자 노력하는 것이 좋음.
04. this
실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장
실행 컨텍스트 활성화 당시, this가 지정되지 않는 경우: this ← 전역 객체가 저장
05. 정리
- 실행 컨텍스트
- 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
- 전역 공간에서 자동으로 생성되는 전역 컨텍스트와 eval 및 함수 실행에 의한 컨텍스트 등
- 활성화 시점에 VariableEnv, LexicalEnv, ThisBinding 정보를 수집
- VariableEnv, LexicalEnv
- 공통: 실행 컨텍스트 생성 당시 동일 내용
- 차이
- LexicalEnv: 함수 실행 도중에 변경되는 사항이 즉시 반영
- VariableEnv: 초기 상태를 유지
- 구성
- environmentRecord: 매개변수명, 변수의 식별자, 선언한 함수의 함수명 수집
- outerEnvironment-Reference: 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성
- 호이스팅
- 코드 해석을 수월하게 해 environmentRecord의 수집 과정을 추상화
- 실행 컨텍스트가 관여하는 코드 집단의 최상단으로 이들을 끌어올린다고 해석하는 것
- 변수 선언과 값 할당이 동시에 이뤄진 문장: 선언부만 호이스팅, 할당 과정(호출부)는 원래 자리에.
- 스코프
- 변수의 유효범위
- outerEnvironmentReference: 해당 함수가 선언된 위치의 LexicalEnvironment를 참조
- 코드 상에서 어떤 변수에 접근하는 경우 현재 컨텍스트의 LexicalEnvironment를 탐색해서
- 발견 : 그 값을 반환
- 발견 못하면 다시 outerEnvRef에 담긴 LexicalEnv를 탐색하는 과정을 거침
- 전역 컨텍스트까지 탐색해도 해당 변수를 찾지 못하면 undefined를 반환
- 전역변수와 지역변수
- 전역변수: 전역 컨텍스트의 LexicalEnv에 담긴 모든 변수
- 그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수들
- 안전한 코드 구성을 위해 전역변수 사용 최소화