실행컨텍스트 Execution Context
실행 컨텍스트란 코드가 실행되기 위해 필요한 환경입니다.
코드를 실행하기 위해서 자바스크립트 엔진은 아래의 정보를 알고 있어야 합니다.
- 변수 : 전역변수, 지역변수, 매개변수, 객체의 프로퍼티
- 함수 선언
- 변수의 유효범위 (스코프)
- this
이 같은 실행에 필요한 정보들을 형상화, 구분하기 위해 자바스크립트 엔진은 실행 컨텍스트를 물리적 객체의 형태로 관리합니다.
자바스크립트 파일이 로드되면 전역 컨텍스트가 생성되어 콜 스택에 쌓입니다. 코드가 실행되고 func1 함수를 만나면 func1의 환경 정보를 담은 함수 컨텍스트가 생성되어 콜스택에 쌓입니다. 그리고 이어서 func2가 쌓입니다.
func2의 코드 실행이 완료되면 func2의 함수 컨텍스트가 콜 스택에서 사라지고 func1의 코드가 실행됩니다. 이후에 func1의 함수 컨텍스트도 콜 스택에서 사라지고 전역 컨텍스트에서의 코드가 마저 실행됩니다. (LIFO - Last In First Out) 페이지가 종료되면 전역 컨텍스트도 사라지게 됩니다. 즉 페이지가 종료되지 않으면 전역 컨텍스트는 계속 남아 있게 됩니다.
const a = 1; // 전역스코프
{ const a = 2; // 로컬1스코프
{ const a = 3;
console.log(a)
} //로컬2스코프
}
a값을 찾아가는 과정은 로컬2스코프 -> 로컬1스코프 -> 전역스코프 입니다.
각각의 블럭은 렉시컬환경 이라는 내부 오브젝트를 가지고 있습니다. 렉시컬 환경에는 각각 블럭에 어떤 변수들이 들어있는지, 부모는 누구인지, 같은 정보들을 담아두는 오브젝트가 있습니다.
렉시컬환경 -
환경레코드 (Environment Record) : 현재 블럭에 대한 정보
외부 환경 참조 (Outer Lexical Environment Reference) : 외부 환경에 대한 정보.
어떤 블럭안의 변수에 접근하면, 해당 블럭의 렉시컬환경 속 환경 레코드에 해당 변수가 있는지 없는지 검사를 하게 됩니다. 없다면? 스코프체인 외부렉시컬 환경 참조를 통해 부모 렉시컬환경으로 넘어갑니다. 부모 렉시컬 환경의 환경레코드를 검사하고 없다면 다시 외부렉시컬 환경 참조를 통해 부모의 부모 렉시컬환경으로 넘어가는 것입니다.
=> 메모리 절약 뿐만 아니라, 성능을 위해서라도 변수는 최대한 필요한 곳에서 정의하는게 좋습니다. 찾아가는 과정에서 리소스가 소요되기 때문입니다.
실행컨텍스트는 세가지 프로퍼티를 소유합니다.
변수객체 / 스코프체인 / this
변수객체
실행 컨텍스트가 생성되면
자바스크립트 엔진은 실행에 필요한 여러 정보들을 담을 객체를 생성하는데 이를 Variable Object(VO / 변수 객체)라고 합니다.
해당 객체에는 아래의 정보들이 들어가게 됩니다.
- 변수
- 매개변수(parameter)와 인수 정보(arguments)
- 함수 선언(함수 표현식은 제외)
전역 컨텍스트
전역 컨텍스트에서의 변수객체(Variable Object)는 유일하며 최상위에 위치하고 모든 전역 변수, 전역 함수 등을 포함하는 전역 객체(Global Object / GO)를 가리킵니다. 전역 객체는 전역에 선언된 전역 변수와 전역 함수를 프로퍼티로 소유합니다.
함수 컨텍스트
함수컨텍스트에서 변수객체(Variable Object)는 Activation Object(AO / 활성 객체)를 가리키며 *매개변수와 *인수들의 정보를 배열의 형태로 담고 있는 객체인 arguments object가 추가된다.
매개변수(parameter)란 함수의 정의에서 전달받은 인수를 함수 내부로 전달하기 위해 사용하는 변수를 의미합니다.
인수(argument)란 함수가 호출될 때 함수로 값을 전달해주는 값을 말합니다.
function addNum(x, y, z) { // x, y, z라는 3개의 매개변수를 가지는 함수 addNum()을 정의함.
return x + y + z;
}
addNum(1, 2, 3); // 인수로 1, 2, 3을 전달하여 함수를 호출함. -> 6
스코프체인
스코프 체인은 해당 전역 또는 함수가 참조할 수 있는 변수, 함수 선언 등의 정보를 담고 있는 전역 객체(GO) 또는 활성 객체(AO)의 리스트를 가리킵니다.
현재 실행 컨텍스트의 활성 객체(AO)를 선두로 하여 순차적으로 상위 컨텍스트의 활성 객체(AO)를 가리키며 마지막 리스트는 전역 객체(GO)를 가리킵니다.
스코프 체인을 통해 렉시컬 스코프를 파악합니다. 함수가 중첩 상태일 때 하위함수 내에서 상위함수의 스코프와 전역 스코프까지 참조할 수 있는데 이것는 스코프 체인을 검색을 통해 가능합니다. 함수가 중첩되어 있으면 중첩될 때마다 부모 함수의 Scope가 자식 함수의 스코프 체인에 포함됩니다. 함수 실행 중에 변수를 만나면 그 변수를 우선 현재 Scope, 즉 Activation Object에서 검색해보고, 만약 검색에 실패하면 스코프 체인에 담겨진 순서대로 그 검색을 이어가게 되는 것입니다. 이것이 스코프 체인이라고 불리는 이유입니다.
스코프 체인은 식별자 중에서 객체(전역 객체 제외)의 프로퍼티가 아닌 식별자, 즉 변수를 검색하는 메커니즘입니다. (class, 오브젝트, 생성자함수의 프로퍼티가 아닌 , 변수를 검색하는 메커니즘.)
식별자 중에서 변수가 아닌 객체의 프로퍼티(물론 메소드도 포함된다)를 검색하는 메커니즘은 프로토타입 체인(Prototype Chain)입니다.
this
스코프 체인 생성이 완료되면, this에 값이 할당됩니다. this(웹 브라우저에서는 window)는 전역 객체를 가리키고 있다가 함수 컨텍스트가 실행될 때 함수 호출 패턴에 의해 this에 값이 할당됩니다.
+
let apple = '사과';
{
apple = '🍎';
}
console.log(apple); // 🍎
let apple = '사과';
{
let apple = '🍎';
}
console.log(apple); // 사과
위의 경우 전역 스코프에 apple을 선언했기 때문에 블록 안에서도 접근이 가능하고, 블럭안의 apple은 곧 전역변수의 apple과 같은 변수이며, 재할당 되었습니다. 선언이 없었기 때문에 부모를 찾으러 간 것입니다.
아래의 경우 두 apple의 스코프가 다르기 때문에 다른 변수 입니다.
호이스팅 Hoisiting
호이스팅이란 코드가 실행되기 전에 브라우저가 모든 변수들의 선언을 먼저 하는 과정입니다.
자바스크립트 엔진이 코드를 실행하기 전, 변수, 함수, 클래스의 선언문을 끌어 올리는 것을 말합니다.
변수의 선언과 초기화를 분리한 후, 선언만 코드의 최상단으로 옮깁니다.
let a = 0; 이라고 하면, let a; 라는 선언만 최상단으로 옮기고, a = 0은 그대로 두는 것입니다.
console.log(hi);
let hi = “hi”
위 코드는 실행되지 않습니다. let hi; 는 호이스팅 되지만, hi = “hi”는 되지 않기 때문입니다.
print()
function print() {
console.log(“print”)
};
함수의 경우 선언이 아닌 표현식이라면 실행되지 않습니다.
위의 코드는 실행되지만, 아래코드는 실행되지 않습니다.
print()
let print = function () { console.log(“hi”); }
변수(let, const) 와 클래스는 선언만 호이스팅이 되고, 초기화는 되지 않기 때문입니다.
초기화 전, 변수에 접근하면 컴파일(빌드) 에러가 발생합니다.
let cat = new Cat();
class Cat{}
class의 경우도 마찬가지 입니다.
let x = 1;
{ console.log(x);
let x = 2;
}
전역 변수 x를 찾아 나서기 전에, 블럭안에서 x값이 선언이 되었기 때문에 let x 까지만 선언이 되었고,
값은 초기화 되지 않은 상태로 받아 들여져 에러가 납니다.
호이스팅으로 인한 것.
자바스크립트 엔진이 코드를 실행하기 전, 변수, 함수, 클래스의 선언문을 끌어 올리는 것을 말합니다.
변수의 선언과 초기화를 분리한 후, 선언만 코드의 최상단으로 옮깁니다.
let a = 0; 이라고 하면, let a; 라는 선언만 최상단으로 옮기고, a = 0은 그대로 두는 것입니다.
x라는 값이 블럭 내부에 존재하기 때문에 부모의 값을 찾지 않았고, 값이 없는 상태인 것입니다.