TIL 028 | 코어 자바스크립트 - 실행 컨텍스트
코어 자바스크립트를 읽으며 JavaScript를 제대로 뿌셔보겠다던 다짐... 1장부터 매우 고생을 했다. 기존의 지식들은 작은 종이조각 같았고 읽고 또 읽으면서 겨우 포스팅을 진행했다. 이번에는...
실행 컨텍스트
를 만나게 됐는데... 여전히 👀 어렵지만 잘 정리해보자.
📋 실행 컨텍스트란?
실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로서, 자바스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념이다.
먼저 간단하게 스택(stack)와 큐(queue)의 개념을 살펴보면 스택은 출입구가 하나뿐인 깊은 우물 같은 데이터 구조로서 넣는 순서 a,b,c,d라면 꺼낼 수 있는 순서는 d,c,b,a의 구조이다.
반면, 큐는 양쪽이 모두 열려있는 파이프의 구조를 생각하면 되는데 보통 한 쪽은 입력, 반대 쪽은 출력을 담당하므로 a,b,c,d 순서로 넣는다면 a,b,c,d 순으로 꺼내게 된다고 생각하면 된다.
동일한 환경에 있는 코드들을 실행할 때 필요한 정보들을 모아 컨텍스트를 구성하고 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여 있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드와 환경과 순서를 보장한다.
여기서 동일한 환경이란 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, 함수 등이 있다.
실행 컨텍스트와 콜스택
// -------------------------(1) 전역 컨텍스트
var a = 1;
function outer() {
function inner() {
console.log(a); // undefined
var a = 3;
}
inner(); // --------------(2) inner 함수 컨텍스트
console.log(a); // 1
}
outer(); // ----------------(3) outer 함수 컨텍스트
console.log(a);
위의 예시를 보면서 콜 스택에 실행 컨텍스트가 어떤 순서로 쌓이고, 어떤 순서로 실행에 관여하는지 확인해 보자.
- 자바스크립트 코드를 처음 실행하는 순간
(1)
전역 컨텍스트가 콜스택에 담긴다. - 콜스택에 전역 컨텍스트만 존재하므로 순차적으로 관련된 코드들을 순차로 진행하다가
(3)
에서outer
함수를 호출하면 자바스크립트 엔진은outer
관련 환경 정보를 수집해서 outer 실행 컨텍스트를 생성한 뒤에 콜스택에 담는다. - 콜스택 맨 위에 outer 실행 컨텍스트가 놓였으므로 전역 컨텍스트와 관련된 코드의 실행을 일시중단하고 대신 outer 실행 컨텍스트와 관련된 코드들을 실행한다. (함수 내부의 코드)
- 마찬가지로
outer
함수 내부의 코드를 실행하다가(2)
에서inner
함수를 호출하면 outer 실행 컨텍스트를 중단하고 생선된 inner 실행 컨텍스트를 진행할 것이다. - 즉, inner 함수 내부에서 a 변수에 값 3을 할당하고 나면 inner 함수 실행컨텍스트가 콜스택에서 제거되고, 아래에 있던 outer 컨텍스트가
(2)
의 다음부터 진행된다. a 변수의 값을 출력하고 나면 outer 컨텍스트도 종료되기 때문에 콜 스택에 남아있는 전역 컨텍스트를 실행하고 제거한 뒤에 종료되게 된다.
다소 복잡해 보일 수 있지만 쉽게 말해 콜스택에 전역, 함수 컨텍스트 등을 차곡차곡 쌓은 뒤에 맨 위의 컨텍스트부터 차례로 해결해 나간다고 생각하면 된다. 그림으로 표현하면 다음과 같다.
객체 내 수집정보
VariableEnviroment
: 선언 시점의 식별자 정보 + 외부 환경 정보enviormentRecord(snapshot)
outerEnvironmentReference(snapshot)
LexicalEnvironment
: 변경사항이 실시간으로 반영enviormentRecord
outerEnvironmentReference
ThisBinding
: this 식별자가 바라봐야 할 대상 객체
📎 VariableEnvironment
VariableEnviroment
는 현재 컨텍스트 내의 식별자들에 대한 정보와 외부환경에 대한 정보가 담긴다. 여기까지 내용은 LexicalEnvironment
와 동일하나 최초 선언 시점의 스냅샷을 유지한다는 점이 차이가 있다. 처음에 만들어 낸 원본이라고 생각하면 쉽다.
실행 컨텍스트를 생성할 때 VarialbleEnviroment
에 정보를 먼저 담은 다음에, 이를 복사해서 LexicalEnvironment
를 만들고 이후에는 LexicalEnvironment
를 주로 사용한다.
🖇 LexicalEnvironment
VariableEnviroment
의 정보를 그대로 가져오기 때문에 최초에는 동일하지만 이후 변동사항이 여기에 담긴다.
environmentRecord와 호이스팅
environmentRecord
에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훓어나가며 순서대로 수집한다.
💡 Point!
전역 실행 컨텍스트의 경우는 변수 객체를 생성하지 않고 자바스크립트 구동 환경이 별도로 제공하는 전역 객체를 활용한다. 전역 객체로는 브라우저의 window, Node.js의 global 객체 등이 있다. 이들은 자바스크립트 내장 객체가 아닌 호스트 객체로 분류된다.
변수 정보를 수집하는 과정을 모두 마쳤어도 아직 실행 컨텍스트가 관여할 코드들은 실행되기 이전의 상태이다. 코드가 실행되기 전에 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있게 되는 셈이다. 그렇기 때문에 "자바스크립트 엔진의 경우 식별자들을 최상단으로 끌어올려놓은 당므에 실제 코드를 실행한다" 라고 생각할 수 있고 이것이 호이스팅(hoisting) 이다.
호이스팅 규칙
function a (x) { // 수집 대상 1(매개변수)
console.log(x); // (1)
var x; // 수집 대상 2(변수 선언)
console.log(x); // (2)
var x = 2; // 수집 대상 3(변수 선언)
console.log(x); // (3)
}
a(1);
예시를 통해서 살펴보자. 위의 코드를 보면 진행 순서에 따라서 (1) 1, (2) undefined, (3) 2
가 출력될 것으로 예상할 수 있다. 하지만 실제 결과는 어떻게 나오는지 보자.
function a () {
var x; // 수집 대상 1의 변수 선언 부분
var x; // 수집 대상 2의 변수 선언 부분
var x; // 수집 대상 3의 변수 선언 부분
x=1; // 수집 대상 1의 할당 부분
console.log(x); // (1)
console.log(x); // (2)
x=2; // 수집 대상 3의 할당 부분
console.log(x); // (3)
}
a(1);
호이스팅을 고려하여 가상으로 코드의 순서를 조정한다면 위처럼 된다. environmentRecord
는 현재 실행될 컨텍스트의 대상 코드 내의 식별자에만 관심이 있기 때문에 값의 할당은 관심이 없다. 따라서 변수의 선언만 끌어올리고 할당 과정은 제자리에 남는다.
따라서, 실제로 출력되는 결과는 1, 1, 2
가 출력된다.
function a() {
console.log(b); // (1)
var b = 'bbb'; // 수집 대상 1(변수 선언)
console.log(b); // (2)
function b(){} // 수집 대상 2(함수 선언)
console.log(b); // (3)
}
a();
두번째 예시를 보자. 위의 코드를 보면 결과로 undefined, 'bbb', b함수
가 출력될 것으로 예상된다.
function a () {
var b; // 수집 대상 1. 변수는 선언부만 끌어올림.
var b = function b() {} // 수집 대상 2. 함수는 선언 전체를 끌어올림.
console.log(b); // (1)
b = 'bbb'; // 변수의 할당부는 원래 자리에 남겨둠.
console.log(b); // (2)
console.log(b); // (3)
}
a();
해석의 편의를 위해서 호이스팅 후에 함수 선언문을 함수명으로 선언한 변수에 함수를 할당한 것처럼 변경했다.
출력의 결과는 b함수, 'bbb', 'bbb'
이 나왔다.
💡 Point!
함수의 경우에는 선언 전체를 끌어올리기 때문에 함수 선언의 경우 위치와 상관없이 어디서든 호출할 수 있게 된다. 편리할 수도 있지만 필연적으로 문제를 만들 수 밖에 없기 때문에 다른 표현 방법에 대해서 알아보자.
함수 선언문과 함수 표현식
자바스크립트에서 함수를 표현하는 방식 중에서 세 가지에 대해서 이야기를 해보자.
함수 선언문
function a () {
// about...
}
a(); // 실행 ok
function 정의부만 존재하고 별도의 할당 명령이 없는 경우이다.
함수 표현식
var b = function () { /* about... */ }; //(익명) 함수 표현식
b(); // 실행 ok
var c = function d () { /* about... */ }; //(기명) 함수 표현식
c(); // 실행 ok
d(); // 에러!
함수 표현식에는 기명 함수 표현식
, 익명 함수 표현식
등이 있는데 주로 익명 함수 표현식
을 사용한다.
💡 Point!
기명 함수 표현식의 경우에 외부에서는 함수명으로 호출할 수 없다. 이는 예전에 디버깅의 목적에서 사용하던 방식으로 최근에는 모든 브라우저들이 익명 함수 표현식의 변수명을 함수의 name 프로퍼티에 할당하고 있기 때문에 사용의 필요성이 적어졌다.
둘의 차이
console.log(sum(1,2));
console.log(multiply(3,4));
function sum(a, b) { // 함수 선언문 sum
return a + b;
}
var multiply = function(a, b){ // 함수 표현식 mutiply
return a * b;
};
위의 코드를 실행해보면 sum(1,2)
의 경우에는 잘 동작하지만 multiply(3,4)
의 경우에는 에러가 나온다. 함수 선언문의 경우 전체를 호이스팅 하지만 함수 표현식의 경우에는 변수 선언만 호이스팅 되기 때문이다.
여기서 어디서든 호출이 가능한 함수 선언문을 편리하게 느낄 수도 있지만 실제로는 그렇지 않다. 프로그래밍에서 자유가 커진다는 것은 그만큼 실수를 할 확률도 늘어난다는 뜻이다.
원활한 협업을 위해서 전역 공간에 함수를 선언하거나, 동명의 함수를 중복 선언하는 것은 좋지 않은 일이지만 함수 표현식을 통해 정의를 했다면 큰 문제가 발생하지 않을 수 있다.
🔗 스코프, 스코프 체인, outerEnvironmentReference
스코프(scope)란 식별자에 대한 유효범위이다. 쉽게 말해서 밖에서는 안을 볼 수 없고 안에서는 밖을 볼 수 있음을 뜻한다. 자바스크립트는 ES5까지는 함수에 의해서만 스코프가 생성됐으나 ES6부터 let
과 const
의 등장으로 블록 레벨 스코프를 생성할 수 있게 됐다.
스코프 체인
식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인이라고 한다. 이를 가능하게 하는 것이 outerEnvironmentReference
이다.
outerEnvironmentReference
는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment
를 참조한다. 그렇기 때문에 가장 가까운 요소부터 차례로 접근이 가능하다. 이런 구조적 특성으로 여러 스코프에서 동일한 식별자를 선언한 경우에 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 된다.
var a = 1;
var outer = function () {
var inner = function () {
console.log(a); // (1)
var a = 3;
};
inner();
console.log(a); // (2)
};
outer();
console.log(a); // (3)
위 코드를 보면 (1)에서 undefined
가 출력된다. 스코프 체인을 이용해서 검색을 할 때, inner
스코프의 LexicalEnvironment
를 참조하게 되고 a
식별자가 있기 때문에 이를 반환하기 때문이다. 한마디로 이미 inner 함수 안에서 a
를 선언했기 때문에 밖에 존재하는 식별자 a
에는 접근할 수 없게 된다. 이를 변수의 은닉화라고 한다.
전역변수와 지역변수
전역 변수
: 전역 공간에서 선언한 변수.지역 변수
: 함수 내부에서 선언한 변수.
스코프 오염을 최소화하기 위해서는 최대한 지역 변수를 사용하고 전역 변수의 사용을 쵯소화 하는 것이 좋다.
마치며!✨
단숨에는 잘 이해되지 않는 실행 컨텍스트에 대해서 정리했다. 실행 컨텍스트에 대한 이해가 없이 코드를 작성하면 알 수 없는 동작으로 고생을 할 수 있기 때문에 어렵더라도 제대로 다시 이해해보고 넘어가려고 한다. 다음 차례는 항상 다른 모습으로 괴롭히는this
이다. 여전히 날 괴롭히는this
도 한번 뿌셔보자.
Author And Source
이 문제에 관하여(TIL 028 | 코어 자바스크립트 - 실행 컨텍스트), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jun_n3/TIL-028-코어-자바스크립트-실행-컨텍스트저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)