INTRO
개발을 진행하면서, 모호했던 javascript의 개념들을
다시 한번 정리하기 위한 포스팅이다.
자바스크립트의 실행 컨텍스트(Execution Context)에 대해서 알아본다.
0. 서론
- 실행 컨텍스트는 직역하면, 실행 문맥.
- 자바스크립트 엔진은 우리가 작성한 '실행 가능한 코드'를 자신이 처리할 수 있도록
- 논리적으로 구분하고 이를 처리한다.
- 이 때 구분 된 코드들이 실행되는 환경을 실행 컨텍스트라고 한다.
- 아래의 예제를 살펴보면서 간단하게 이해하고 넘어가보자
let name = 'choi';
const getName = () => {
console.log(name);
};
getName();
- 이 코드에서 실행 컨텍스트는 총 2개가 만들어진다.(전역 실행 컨텍스트와 함수 실행 컨텍스트)
- 이름에서 유추 가능하듯이, getName() 함수가 실행되기 위해 '함수 실행 컨텍스트'가 있어야 하며,
- 전역에 선언된 name 변수를 참조하기 위해 '전역 실행 컨텍스트'를(정확히 얘기하면 객체를..) 참조한다.
- 이 실행 컨텍스트에서는 코드를 항상 일관되게 실행하기 위한 일정한 규칙과 특징들이 있다.
- 아래에서 실행 컨텍스트에 대해 더 자세하게 알아본다.
1. 실행 컨텍스트의 종류
- 전역 실행 컨텍스트(Global execution context)
- 기본적으로 생성되는 컨텍스트이다. 함수 외부에 있는 코드들이 이 전역 실행 컨텍스트에 존재한다.
- 페이지가 소멸 될 때 까지(해당 스크립트를 수행하는 애플리케이션이 종료 될 때 까지) 유지된다.
- 이 전역 실행 컨텍스트가 생성되는 시점에서, window객체 생성, this를 windos 객체로 지정 등의 동작이 수행된다.
- 함수 실행 컨텍스트(Functional execution context)
- 모든 함수가 실행될 때는, 이 함수 실행 컨텍스트가 생성된다.
- 함수 내부에 선언된 코드들이 이 컨텍스트 내에 존재한다.(함수 내 중첩 함수의 경우 미 포함)
- 함수가 종료되면 사라진다.
- 이 외에도 eval() 호출시 생성되는 컨텍스트나 다른 스펙들이 있는 것으로 보이나,
- 실제 코드 작성시에는 위 2가지만 알면 될 것 같다.
2. 실행 스택
- 자바스크립트 엔진은 힙 영역과 호출 스택 영역으로 구분된다.
- 이는 다른 언어들에서도 이름만 조금씩 다를 뿐, 비슷한 개념이 많다.
- 메모리 힙 영역에는 선언한 함수, 변수 등이 주소가 지정되면서(메모리가 할당되면서) 저장되고,
- 호출 스택 영역에는 런타임에 호출되는 함수들이 차곡차곡 쌓인다.
(이 외에도 많은 역할과 동작이 일어나나, 본 포스팅의 주제를 벗어나므로 굳이 설명하지 않는다)
- 여기서 갑자기 왜 힙이 나오고 스택이 나올까?
- 바로 이 호출 스택에 실행 컨텍스트들이 쌓이고,
- 가장 최 상단에 존재하는 실행 컨텍스트가 실행되면서
- 우리가 작성한 코드를 실행한다.
(어느 시점에서든 '실행 중인' 실행 컨텍스트는 무조건 1개이다)
- 이 원리를 잘 설명한 ES6 규격 문서의 내용이 있어 가져와봤다.
An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation.
At any point in time, there is at most one execution context that is actually executing code.
- 어느 시점에서든 실제로 코드를 실행하는 실행 컨텍스트는 최대 하나입니다.
This is known as the running execution context.
- 이를 실행 중인 실행 컨텍스트라고 합니다.
A stack is used to track execution contexts.
The running execution context is always the top element of this stack.
- 실행 중인 실행 컨텍스트는 항상 이 스택의 최상위 요소입니다.
A new execution context is created whenever control is transferred from the executable code associated with the currently running execution context to executable code that is not associated with that execution context.
The newly created execution context is pushed onto the stack and becomes the running execution context.
-새로 생성된 실행 컨텍스트는 스택에 푸시되고 실행 중인 실행 컨텍스트가 됩니다.
- 이를 좀 더 이해하기 쉽게 풀어보자면, 아래의 소스코드는 그 아래의 그림처럼 동작한다
- 전역 실행 컨텍스트(Global Execution Context) 는 이 스택이 생성되면서 자동으로 추가된다.
- 아래와 같은 스택의 특성을 이용한 순차적인 동작으로 인해 스코프 체이닝, 호이스팅, 클로저 개념들이 등장한다.
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
3. 실행 컨텍스트의 구성 요소
- 실행 컨텍스트가 스택에 쌓이는 시점에, 렉시컬 환경과 변수 환경이 생성된다.
- 이 환경 내에 우리가 선언한 변수, 함수의 매개변수, 다른 스코프를 참조하는 규칙인 스코프 체인, this에 대한 정보가 들어가고,
- 위에서 언급한 스코프 체이닝, 호이스팅, 클로저 등의 세부적인 방법들은 이 환경에서 수행된다.
- 이 부분은 자세히 들어가면 또 하나의 포스팅이 될 정도의 분량이기에.. 추후에 다루도록 하고,
- '실행 컨텍스트가 이런 개념이구나' 정도만 알고 넘어가도 좋을 것 같다.
(아래 그림은 Functional Execution Context라고 나와있지만, Global Execution Context도 구성 요소는 같다.)
4. 그래서 어떻게 내가 짠 코드가 실행될까?
- 이 부분은 제로초님의 블로그가 이해하는데 많은 도움이 되었다.
- 제로초님은 이 컨텍스트를 객체 형식으로 표현했는데, 이 표현을 인용하여 설명해보도록 하겠다.
- 우선 아래의 코드를 보자
var name = 'choi';
function hasParam(word) {
console.log(word + ' ' + name);
}
function emptyParam() {
var name = 'park';
console.log(name);
hasParam('hello');
}
emptyParam();
- 이 코드에서 전역에 선언되어있는 name, hasParam, emptyParam 이 3가지가 전역 컨텍스트의 변수 배열에 들어간다.
- 전역 컨텍스트이므로, 전역에 전달할 argument는 없다.
'전역 컨텍스트': {
변수객체: {
arguments: null,
variable: ['name', 'hasParam', 'emptyParam'],
},
scopeChain: ['전역 변수객체'],
this: window,
}
- 이제 emptyParam() 함수가 실행되어야 한다.
- 전역 컨텍스트 위에 emptyParam()함수의 함수 컨텍스트가 쌓인다.
- 스코프 체인을 보면, 전역 변수객체가 쌓여있는 것을 볼 수 있다.
- 이를 통해 상위 스코프인 전역 컨텍스트의 변수 객체를 참조할 수 있다.
- 다만 이 경우는 emptyParam() 함수 내에도 name 변수가 선언되어있기때문에 name 변수에 대한 스코프 체이닝이 일어나지 않는다.
- 따라서 emptyParam() 함수 내의 console.log에는 park이 찍히게 된다.
'emptyParam 컨텍스트': {
변수객체: {
arguments: null,
variable: ['name'],
},
scopeChain: ['emptyParam 변수객체', '전역 변수객체'],
this: window,
}
- 이제 hasParam() 함수가 실행되어야 한다.
- 전역 컨텍스트 위에 emptyParam()함수의 함수 컨텍스트 위에 hasParam() 함수의 함수 컨텍스트가 쌓인다.
- scope chain을 보면, 전역 변수객체가 쌓여있는 것을 볼 수 있다.
- 왜 전역 변수객체만 쌓여있지? emptyParam의 변수객체가 쌓여야 하는 것 아닌가? 라고 생각했다면,
- emptyParam, hasParam 모두 lexical scoping에 따라 선언 시에 scope chain이 정해진다.( 이 부분에 대해선 별도 포스팅 예정)
- 다시 돌아와서, 이를 통해 상위 스코프인 전역 컨텍스트의 변수 객체를 참조할 수 있다.
- 따라서 hasParam() 함수 내의 console.log에는 choi가 찍히게 된다.
'hasParam 컨텍스트': {
변수객체: {
arguments: [{ word : 'hello' }],
variable: null,
},
scopeChain: ['hasParam 변수객체', '전역 변수객체'],
this: window,
}
- 이후 hasParam 함수가 종료되면서 스택에서 pop되고,
- emptyParam 함수도 종료되며 스택에서 pop된다.
- 우리가 짠 코드는 이렇게 동작한다.
마무리
자바스크립트의 핵심 개념인 실행 컨텍스트에 대해 정리해봤다.
공부하면서 내용이 복잡한 느낌이 있었는데, 계속 보다보니 이해가 되었다.
요약하자면, 기존 다른 언어들의 힙, 스택 개념에 실행 컨텍스트의 세부적인 개념이 한겹 더 덮혀있다는 느낌을 받았다.
- 출처 :
https://blog.sessionstack.com/how-does-javascript-actually-work-part-1-b0bacc073cf
https://m.blog.naver.com/dlaxodud2388/222655214381
https://www.zerocho.com/category/JavaScript/post/5741d96d094da4986bc950a0
-퍼가실 때는 출처를 꼭 같이 적어서 올려주세요!
'Dev > [Javascript]' 카테고리의 다른 글
[javascript] 렉시컬 환경(Lexical Environment) (0) | 2023.01.17 |
---|---|
[javascript] 스코프(Scope) (0) | 2023.01.16 |
[javascript] ES6 - 화살표 함수(Arrow function) (0) | 2023.01.07 |
[javascript] ES6 - 템플릿 리터럴(template literals) (0) | 2023.01.04 |
[javascript] var, let, const 차이점 (0) | 2023.01.03 |