실행 컨텍스트와 콜스택
실행 컨텍스트(execution context)는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다. 동일한 환경에 있는 코드들을 호출 스택(콜 스택)에 쌓아뒀다가 가장 위에서부터 하나씩 실행하는 것입니다. 실행 컨텍스트를 구성할 수 있는 방법으론 전역 공간, eval(), 함수 등이 있습니다. 전역 공간(전역 컨텍스트)은 자동으로 생성되고 eval은 활용 빈도가 낮으므로 실행 컨텍스트를 구성하는 방법 = 함수 실행이라고 보아도 무방합니다. 컨텍스트라는 영어가 이해되지 않는다면 함수가 실행되는 '맥락'(context)으로 이해하면 이해가 더 수월합니다.
앞으로 살펴보겠지만 함수 실행 = 실행 컨텍스트 생성 = this 결정이라고 알아둡시다.
기억합시다. 함수가 실행되면 해당 함수의 실행 컨텍스트가 실행됩니다.
function a() {
function b() {
function c() {
console.log("c");
}
c();
console.log("b")
}
b();
console.log("a");
}
a();
다음과 같은 코드가 있을 때, 실행 과정과결과는 어떻게 될까요?
ⓐ 우선 처음 실행된 순간 전역 컨텍스트가 콜스택에 담깁니다. 현재는 아무것도 없으므로 아무 것도 담기지 않습니다. 전역 컨텍스트는 그냥 js가 실행되는 순간 활성화 된다고 이해합시다.
ⓑ 함수 선언만 하고 호출은 하지 않았으므로 코드를 타고 쭉 내려오다가 a()를 만남으로써 전역 컨텍스트의 실행을 잠시 멈추고 a 함수에 대한 환경 정보(변수가 뭔지, 참조는 어디를 하는지와 같은 그냥 내용)를 수집해서 컨텍스트 객체에 저장한 후 콜 스택에 담습니다.
ⓒ a 실행 컨텍스트 내부를 JS 엔진이 읽어내려가다가 b()를 만남으로써 b 실행 컨텍스트가 콜 스택에 담깁니다.
ⓓ b 실행 컨텍스트 내부를 JS 엔진이 읽어 내려가다가 c()를 만남으로써 c 실행 컨텍스트가 콜 스택에 담깁니다.
ⓔ 실행 컨텍스트 내부에는 실행될 함수가 없으므로 새로운 실행 컨텍스트가 생기지 않고 c 실행 컨텍스트의 내용을 처리합니다. 여기서는 "c"를 출력합니다.
ⓕ 콜 스택에 쌓인 대로 b, a, 전역 컨텍스트를 실행합니다.
실행 컨텍스트 객체
그런데, 앞서 ⓑ 단계에서 함수에 대한 환경 정보를 수집해서 실행 컨텍스트 객체에 저장한 후 콜 스택에 쌓는다고 하였습니다. 이 실행 컨텍스트가 활성활 될 때 (실행될 때) 자바스크립트 엔진은 해당 실행 컨텍스트 객체를 활용합니다.
그렇다면 이 실행 컨텍스트 객체는 무엇으로 이루어져 있는지 확인해봅시다. 이 객체는 JS 엔진이 활용할 목적으로 생성한 것이므로 개발자라 코드를 통해 확인할 수는 없습니다. 개념적으로 이해할 수 있을 뿐입니다.
실행 컨텍스트 객체는 VariableEnvironment와 LexicalEnvironment로 구별되며 각각 environmentRecord와 outer-EnvironmentReference로 구성되어 있습니다. VariableEnvironment에 담기는 건 일종의 스냅샷이기에 자주 활용하는 건 LexicalEnvironment입니다.
LexicalEnvironment는 현재 컨텍스트 내부에 무슨 식별자가 있으며 무엇을 참조하도록 되어있는와 같은 환경 정보들을 모아놓은 것입니다. environmentRecord와 outer-EnvironmentReference로 구성되어 있습니다.
environmentRecord에는 현재 컨텍스트와 관련된 식별자 정보 들이 저장됩니다. 코드가 순서대로 실행되는 것처럼 컨텍스트 내부 전체를 처음부터 끝까지 순서대로 훑어나가며 식별자들을 수집합니다. 물론 수집만할 뿐이지 실행시키지는 않습니다. 이 때문에 JS 엔진은 함수를 실행하거나 변수를 넣지는 않았지만 모든 곳의 식별자를 이미 알고 있습니다. 다른 방식으로 생각해보면, 함수 내부에 있는 식별자나 함수들을 최상단으로 끌어 올리는 셈입니다. 이런 개념을 hoisting(호이스팅)이라고 합니다. JS 엔진이 변수들을 모두 이해하고 있다는 것을 마치 최상단으로 끌어올린 것처럼 생각하자는 것이죠.
호이스팅
* 변수는 선언부만 호이스팅되며 / 함수 선언문은 통째로 호이스팅되고 / 함수 표현식은 변수처럼 선언부만 호이스팅됩니다.
앞서 JS 엔진이 컨텍스트들을 훑으면서 변수나 함수들을 environmentRecord에 저장한다고 했습니다. 때문에 JS 엔진은 이미 모든 변수들을 알고 있어서 호이스팅이 됩니다. 쉽게 말해서 선언 전에 활용할 수 있다는 것입니다. 예시를 좀 보겠습니다. (호이스팅을 이해하더라도 무언가를 사용하기 위해서는 반드시 먼저 변수를 설정하는 것이 좋습니다.)
좌의 함수에서는 x에 선언만 했음에도 호이스팅을 통해 5의 값을 자동으로 할당받으며 재선언 하면서 바뀌었습니다.
가운데 함수에서는 함수 또한 호이스팅 되어서 출력됨을 볼 수 있습니다. 마지막으로, 우측 함수에서는 함수 선언 전에 사용했음에도 결과가 정상적으로 출력됩니다.
좌측 함수를 자세히 풀자면 다음과 같습니다.
function a() {
var x; // 함수 파라미터로 준 x
var x;
var x;
x = 5; // 함수의 파라미터로 할당한 값은 최초로 할당됩니다.
console.log(x);
console.log(x);
x = 2;
console.log(x)
}
가운데 함수를 자세히 풀자면 다음과 같습니다.
function a() {
var b // 변수는 선언부만 호이스팅됩니다.
function b() {} // 함수 선언문은 통째로 호이스팅됩니다.
console.log(b)
b = "bbb" // 변수의 정의 부분에서야 할당됩니다.
console.log(b)
console.log(b)
}
함수가 호이스팅되어서 순서와 상관 없이 함수를 사용할 수 있게 되었습니다만 이 문제는 간혹 문제를 일으키기도 합니다. 선언 전에 활용할 수 있다는 것은 생각보다 좋지 않은 특성입니다.
예를 들어 어떤 함수를 정의하고 사용하다가 다른 개발자가 동일한 이름의 함수를 한참 후의 코드에 추가했다고 생각해봅시다. 그렇게 되면 추가된 이후의 코드에만 해당 함수가 적용되는 것이 아니라 상단 부분에까지 적용되어 실행되므로 결과가 망가지게 될 것입니다. (함수 선언문일 때 한정) 이름을 잘 지어야 하는 이유이기도 하고 호이스팅을 의도적으로 이용해서는 안되는 이유이기도 합니다.
함수 선언문과 함수 표현식
함수 표현식은 실제 실행 흐름이 해당 함수에 도달했을 때 함수를 생성합니다. 따라서 실행 흐름이 함수에 도달했을 때부터 해당 함수를 사용할 수 있습니다.
// 함수 선언문
function sum(a, b) {
return a + b;
}
// 함수 표현식
let sum = function(a, b) {
return a + b;
};
위 예시를 이용해 설명해 보도록 하겠습니다. 스크립트가 실행되고, 실행 흐름이 let sum = function…의 우측(함수 표현식)에 도달 했을때 함수가 생성됩니다. 이때 이후부터 해당 함수를 사용(할당, 호출 등)할 수 있습니다. 하지만 함수 선언문은 조금 다릅니다.
함수 선언문은 함수 선언문이 정의되기 전에도 호출할 수 있습니다. 따라서 전역 함수 선언문은 스크립트 어디에 있느냐에 상관없이 어디에서든 사용할 수 있습니다.
호이스팅의 관점에서 이야기하자면 호이스팅할 시 함수 선언문은 통째로 올라오는 반면 함수 표현식은 변수에 할당하는 것(함수를 값처럼 다루는 것)이므로 선언부만 호이스팅됩니다.
위에서와 같이 이름이 중복되어 호이스팅되는 것을 방지되지 위해서 함수 표현식을 이용하는 것이 바람직합니다. 그러나 각자 장단이 있으므로 사용 목적에 따라 적절하게 사용합시다.
// 함수 선언문
function a() {
return "test"
}
a(); // 실행
// 함수 표현식
const b = function() {
return "test"
}
b(); // 실행
// 섞어쓰기
const c = function d() {
return "test"
}
c(); // 실행
d(); // 에러
'Programming Language > 🟨 Javascript (Core)' 카테고리의 다른 글
this(2) : 명시적으로 this를 바인딩하기 (0) | 2020.04.15 |
---|---|
this(1) : this에 대한 모든 것 (0) | 2020.04.15 |
실행 컨텍스트(2) : 스코프, 스코프체인 (0) | 2020.04.15 |
데이터 타입, immutable와 deep Copy, freeze, 메모리 (0) | 2020.04.14 |
코어 자바스크립트 : 소개 (0) | 2020.04.14 |