자바스크립트에서의 실행 컨텍스트는 굉장히 중요한 개념입니다. 실행컨텍스트를 이해하게 되면 Javascript의 동작 원리를 이해할 수 있게 됩니다. 자바스크립트의 동작원리를 이해하게 되면 코드가 에러를 발생하게 되더라도 좀 더 쉽게 예측이 가능해지게 됩니다. 이는 코드를 작성하거나 수정할 때 굉장한 차이점을 가지게 됩니다.
해당 내용이 조금은 어렵게 느껴질 수 있고 광범위하게 느껴질 수도 있지만 천천히 글을 따라서 읽다 보면 충분히 이해할 수 있도록 정리해보겠습니다.
📖 실행 컨텍스트란?
실행 컨텍스트란 코드가 실행되기 위해 필요한 환경 정보들을 모아 놓은 객체입니다.
실행 컨택스트의 동작은, 동일한 환경에 있는 코드들을 실행할 때 필요한 환경정보를 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아놓은 뒤, 가장 위에 있는 컨텍스트와 관련 있는 코드들을 실행하는 것으로 코드의 환경과 순서를 보장합니다.
자바스크립트에서 하나의 실행 컨텍스트를 구성할 수 있는 방법으로는 다음과 같습니다.
- 전역 코드 : 전역 영역에 존재하는 코드
- Eval 코드 : eval 함수로 실행되는 코드
- 함수 코드 : 함수 내에 존재하는 코드
- (ES6부터는) 블록문
가장 쉽게 자바스크립트에서 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것입니다. 어떤 실행 컨텍스트가 활성화될 때 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장합니다. 실행 컨텍스트는 논리적 스택 구조를 가지는데 실행되는 순서대로 콜 스택(call stack)에 쌓였다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 동일한 환경과 순서를 보장합니다.
여기서 나오는 큐와 스택에 대한 개념이 없으면 아래 큐/스택에 대한 글을 짧게 읽고 다시 읽는것을 추천드립니다.
자료구조(큐/스택) 참고 : https://whales.tistory.com/121
💡 실행 컨텍스트와 콜 스택
var x = 'xxx';
function foo () {
var y = 'yyy';
function bar () {
var z = 'zzz';
console.log(x + y + z);
}
bar();
}
foo();
위의 코드를 실행하면, 아래 그림과 같이 실행 컨텍스트가 쌓이고 소멸합니다. (LIFO : Last In First Out)
- 처음 자바스크립트 코드를 실행하는 순간 전역 컨텍스트가 콜 스택에 담깁니다. 최상단의 공간은 코드 내부에서 별도의 실행 명령이 없어도 브라우저에서 자동으로 실행하므로 자바스크립트가 실행되는 순간 전역 컨텍스트는 활성화된다고 볼 수 있습니다. 전역 컨텍스트는 애플리케이션이 종료될 때(웹 페이지에서 나가거나 브라우저를 닫을 때)까지 유지됩니다.
- foo() 함수가 호출되면, 자바스크립트는 foo() 함수에 대한 환경 정보를 수집해서 새로운 실행 컨텍스트를 생성한 후, 전역 컨텍스트 위에 쌓습니다.
- foo() 함수가 실행되다가 내부 함수 bar()를 만나면, 자바스크립트는 bar() 함수의 실행 컨텍스트를 생성합니다. 이 실행 컨텍스트는 스택의 최상단에 쌓이게 됩니다.
- 최상단에 쌓인 bar() 함수가 실행을 종료하면 bar() 함수에 의해 만들어진 실행 컨텍스트는 콜 스택에서 제거됩니다.
- foo() 함수가 실행을 종료하면 foo() 함수에 의해 만들어진 실행 컨텍스트는 콜 스택에서 제거됩니다.
우리가 알고 있는 실행 컨텍스트의 동작으로 스택 구조를 생각해보면, 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 현재 실행할 코드에 관여하게 되는 시점임을 알 수 있습니다. 이제 실행 컨텍스트의 내부에 대해 알아봅시다.
👉 실행 컨텍스트의 3가지 객체
실행 컨텍스트는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이지만 물리적으로는 객체의 형태를 가지며 아래의 3가지 프로퍼티를 소유합니다.
1) VariableEnvironment
현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경사항은 반영되지 않습니다.
- environmentRecord
- outer-EnvironmentReference
2) LexicalEnvironment
처음에는 VariableEnvironment와 같지만 변경사항이 실시간으로 반영됩니다.
- environmentRecord
- outer-EnvironmentReference
3) ThisBinding
this 식별자가 바라보고 있는 대상 객체를 말합니다.
※ 참고
스냅샷(Snapshot) 이란? : 특정 시간에 데이터 저장 장치의 상태를별도의 파일이나 이미지로 저장하는 기술로, 스냅샷 기능을 이용하여 데이터를 저장하면 유실된 데이터 복원과 일정 시점의 상태로 데이터를 복원할 수 있습니다.
VariableEnvironment(V.E)와 LexicalEnvironment(L.E)는 실행 컨텍스트에서 변수의 참조들을 기억하는 환경인데 차이점이라면 V.E에서는 컨텍스트 최초 실행 시점의 스냅샷을 유지하는 반면, L.E에는 변경사항이 반영된다는 점입니다. 일반적으로 함수의 LexicalEnvironment는 해당 함수가 가지는 자신의 로컬 스코프 범위를 말합니다.
이 두 환경의 내부는 다시 EnvironmentRecord와 OuterEnvironmentReference로 구성되어 있습니다.
EnvironmentRecord : 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다.
OuterEnvironmentReference : 호출된 함수가 선언될 당시의 Lexical Environment를 참조하는 포인터로, 스코프 체인을 가능하게 함
👉 EnvironmentRecord의 구성
environmentRecord는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다. 컨텍스트 내부 전체를 처음부터 훑어나가며 순서대로 수집(매개변수 이름, 함수 선언, 변수명 등)합니다. 여기에서 수집되는 식별자들은 매개변수 식별자, 선언된 함수, var로 선언된 변수의 식별자 등이 해당하며 변수 정보를 수집하는 과정을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태입니다.
✔ 함수 선언문과 함수 표현식
console.log(sum(1, 2)); //3
console.log(multiply(3, 4)); // Uncaught TypeError: multiply is not a function
// 함수 선언문
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(multiply(3, 4));
multiply = function (a, b) {
return a * b;
};
함수 선언문은 정의부만 존재하고 별도의 할당 명령이 없기 때문에 함수 전체가 호이스팅 되었습니다.
함수 표현식은 변수로 선언된 선언부만 호이스팅 되었고, 할당부는 원래 자리에 있습니다.
EnvironmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지만을 먼저 수집하기 때문에, 변수를 인식할 때 식별자만 끌어올리고 할당 과정은 원래 자리에 순서대로 남겨둡니다. 이를 호이스팅이라고 합니다.
호이스팅(hoisting)의 개념
"코드가 실행되기 전임에도 불구하고 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알 수 있다"
호이스팅에 대해 보다 자세히 이해하고 싶으신 분들은 이전에 작성한 글을 읽어보면 도움이 됩니다.
https://whales.tistory.com/111
이렇게 식별자 정보를 수집하는 EnvironmentRecord는 구성 요소에 따라 다시 나눠질 수 있습니다.
1) Declarative Environment Record
- 함수 선언, 변수 선언, catch절에서 사용되는 식별자 정보를 담고 있습니다.
- 스코프 내에서 선언된 식별자들의 바인딩을 관리합니다.
- Environment Record를 상속한 서브클래스입니다.
- Function Environment Record
- 함수의 최상위 스코프를 나타내는데 사용되는 선언적 환경 레코드입니다.
- 화살표 함수가 아니라면 this 바인딩을 제공합니다.
- 화살표 함수가 아니고 super를 참조하는 경우 super 메서드를 실행하는데 필요한 state를 제공합니다.
- Module Environment Record
- Module의 외부 스코프를 나타낼 때 사용하는 선언적 환경 레코드입니다.
2) Object Environment Record
- with문과 같이 식별자를 어떤 특정 객체 A의 속성으로 취급할 때 사용합니다.
- 이 경우 A를 binding object라는 속성으로 정의합니다.
- Environment Record를 상속한 서브클래스입니다.
3) Global Environment Record
- 전역 컨텍스트의 경우, object Environment Record의 binding object는 전역 객체(window)를 가리킵니다.
- Object, Array, Function, parseInt 같은 모든 built-in global과, 전역 코드에서의 함수 선언, 제너레이터 선언, 변수 선언에 의해 생성된 모든 식별자 정보는 binding object, 즉 전역 객체에서 찾을 수 있습니다.
- 다시 말해, 선언된 식별자들의 정보를 담고 있는 일반적인 Declarative Environment Record의 역할을 전역 컨텍스트에서는 object Environment Record가 담당합니다.
- Global Environment Record의 declarative Environment Record는 object Environment Record에 포함되지 않은 식별자 정보만 보유합니다.
👉 OuterEnvironmentReference (혹은 outer)
OuterEnvironmentReference를 이해하기 위해서는 스코프(scope) 개념을 먼저 이해해야 합니다. 스코프란 식별자에 대한 유효 범위를 말하며 자바스크립트에는 전역 스코프(global scope)와 지역 스코프(local scope)가 있습니다.
var a = 1;
function scope() { // 함수 스코프
var b = 2;
console.log(a); // 1
console.log(b); // 2
}
console.log(a); // 1
console.log(b); // b is not defined
scope 함수 외부에서 선언한 변수 a는 함수 scope 안에서도 접근이 가능합니다. 그러나 함수 안에서 선언한 변수 b는 오직 함수 안에서만 접근할 수 있습니다. 변수 a는 전역 스코프에, 변수 b는 지역 스코프에 있기 때문입니다. 자바스크립트는 변수의 유효 범위를 검색할 때 안에서부터 바깥으로 찾아나가는데, 이것을 스코프 체인(scope chain)이라고 합니다.
따라서 전역 스코프에 선언된 변수들은 어느 곳에서도 접근이 가능하고 지역 스코프는 선언된 함수의 내부에서만 접근이 가능합니다.
OuterEnvironmentReference는 현재 호출된 함수가 선언되는 시점에서의 Lexical Environment를 참조하는 포인터입니다. OuterEnvironmentReference는 연결 리스트 형태를 띠며 ‘선언 시점의 LexicalEnvironment’를 계속 찾아 올라가는데 '선언 시점의 LexicalEnvironment'라는 건 결국 해당 함수가 속한 상위 스코프의 범위입니다.
각 함수의 OuterEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있기 때문에 가장 가까운 요소부터 위로 차례대로만 접근할 수 있습니다.
var a = 1;
var outer = function () {
var inner = function () {
console.log('inner:', a);
// inner: undefined 출력 (a가 LexicalEnvironment에서 발견되지만, 값이 할당되기 전)
// => 변수는 호이스팅으로 선언이 되었기에 undefined가 출력됩니다.
var a = 3;
};
inner();
console.log('outer:', a);
// outer: 1 출력 (outerEnvironmentReference에서 발견한 값 출력)
};
outer();
console.log('global:', a);
// global: 1 출력 (LexicalEnvironment에서 발견한 값 출력)
위의 코드처럼 스코프 체인을 통해서 상위 스코프로 접근할 수 있음을 알 수 있습니다. 참고로, 전역 컨텍스트에서의 OuterEnvironmentReference는 null입니다.
👉 ThisBinding
실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장됩니다. 실행 컨텍스트 활성화 당시 this가 지정되지 않은 경우 this에는 전역 객체가 저장됩니다. 함수를 호출하는 방법에 따라 this의 값은 달라집니다.
자바스크트에서의 this는 참으로 중요한 역할을 합니다. 이전에 작성한 this 관련 글을 참고로 읽어 보시면 좋을거 같습니다.
자바스크립트에서의 This 알아보기 : https://whales.tistory.com/123
😉 정리
✅ 실행 컨텍스트
- 자바스크립트에서 실행 컨텍스트란, 코드를 실행하기 위해 필요한 정보들을 가진 범위를 객체 형태로 나타낸 것이다.
- 실행 컨텍스트 객체는 활성화 되는 시점에 VariableEnvironment, LexicalEnvironment, ThisBinding의 세 가지 정보 수집합니다.
- 실행 컨텍스트 생성할 때, VariableEnvironment는 초기 상태 유지, LexicalEnvironment는 실행 도중 변경사항 즉시 반영됩니다.
- VariableEnvironment와 LexicalEnvironment는 environmentRecord(매개 변수명, 식별자, 함수명 등 수집)와 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성됩니다.
- LexicalEnvironment는 식별자들에 대한 정보를 담은 EnvironmentRecord와, 상위 LexicalEnvironment를 참조해 스코프 체인을 가능하게 하는 OuterEnvironmentReference 정보로 구성되어 있습니다.
- OuterEnvironmentReference는 현재의 실행 컨텍스트를 구성한 함수가 선언되는 시점에서의 상위 LexicalEnvironment를 참조하기 때문에 식별자의 유효 범위를 상위로 거슬러 올라가 찾게 되는 스코프 체인이 가능합니다.
✅ 호이스팅
- 실행 컨텍스트가 관여하는 코드의 최상단에 끌어올립니다. 단 변수 선언과 할당이 동시에 이루어지면 '선언부'만 호이스팅하며 할당은 이루어 지지 않습니다.
- 함수선언문은 함수 자체가 호이스팅 됩니다.
✅ 스코프
- 변수의 유효범위를 말합니다.
- outerEnvironmentReference는 해당 함수 선언된 위치인 LexicalEnvironment를 참조합니다.
- 전역 컨텍스트의 LexicalEnvironment까지 탐색해도 변수 찾지못하면 undefined 반환합니다.
- 전역변수: 전역 컨텍스트의 LexicalEnvironment 담긴 변수
✅ this
- 실행 컨텍스트 활성화하는 당시 지정된 this가 저장됩니다.
- 지정되지 않으면 전역 객체 저장됩니다.
출처 :
'IT > JavsScript' 카테고리의 다른 글
비동기 처리 방식 알아보기 (Promise, Callback, Async, Await) (0) | 2022.07.14 |
---|---|
이벤트 루프와 태스크 큐 (마이크로 태스크, 매크로 태스크) (1) | 2022.07.13 |
자바스크립트의 형변환 (0) | 2022.07.12 |
자바스크립트 this 이해하기 (feat. call, apply, bind의 차이) (0) | 2022.07.11 |
Lodash 활용법 알아보기 (0) | 2022.06.28 |
제 블로그의 모든 글은 제가 직접 작성 하고 다른 글을 참고할 때는 이전 글보다 읽기 편하게 수정해서 작성하고 있습니다. 커피 한잔 사먹고 더 열심히 좋은글로 보답하겠습니다.
오늘도 제 블로그에 와 주셔서 감사합니다. :)