📚 들어가기 전...
1. 자바스크립트 엔진은 메모리 힙(Memory Heap)과 호출 스택(Call Stack) 두 가지로 구성되어있다.
2. 호출 스택은 코드가 실행될 때 함수의 호출을 저장하는 자료구조로서, 스크립트에서 현재 어떤 함수가 동작하고 있는지, 그 함수 내에서 어떤 함수가 동작하는지, 다음에는 어떤 함수가 호출되어야 하는지 제어하는지 등을 제어하고 기록한다.
⭐️ 호출 스택이 스택 형식이라는 것이 중요하다. 스택은 후입 선출(LIFO, Last-In-First-Out)의 구조를 갖고, 따라서 자바스크립트의 호출 스택에서도 가장 나중에 쌓인 함수가 가장 먼저 실행된다.
호출 스택은 실행 컨텍스트를 차곡차곡 쌓으며 코드를 하나씩 처리한다. 이때 핵심 요소는 실행 컨텍스트이다.
실행 컨텍스트(execution context)는 실행할 코드에 제공할 환경 정보(변수, 함수, 클래스 등의 이름)들을 모아놓은 객체로, 자바스크립트 코드가 실행되는 환경이자, 곧 자바스크립트의 핵심 원리이다.
코드에서 함수가 호출되면 함수 실행에 해당하는 실행 컨텍스트가 생성되고, 자바스크립트 엔진에 있는 콜 스택에 차곡차곡 쌓인다.
그리고 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드를 먼저 실행하고 처리하면서(LIFO), 전체 코드의 환경과 순서를 보장된다.
전역 실행 컨텍스트 : 전역 영역에 존재하는 코드.
프로그램에 단 한 개만 존재하며 실행 컨텍스트의 기본이다.
브라우저의 경우에는 window객체, Node.js의 경우엔 global객체가 곧 전역 실행 컨텍스트가 된다.
함수 실행 컨텍스트 : 함수 내에 존재하는 코드.
함수가 실행될 때마다 만들어지는 실행 컨텍스트이다.
각 함수는 고유의 실행 컨텍스트를 가지며, 함수가 실행되거나 호출될 때에만 생성된다.
자바스크립트 엔진이 스크립트를 처음 마주할 때 전역 컨텍스트를 생성하고, 이를 콜스택에 push 한다.
그 후 엔진이 스크립트를 쭉 읽어내려가면서 함수 호출을 발견할 때마다, 함수의 실행 컨텍스트를 스택에 push 한다.
전역 컨텍스트는 스크립트가 읽히기 시작하는 동시에 만들어지며, 함수 실행 컨텍스트는 함수가 호출되어 실행되기 시작할 때! 만들어진다.
1 const global = "전역변수";
2 const reference = "참조";
3
4 function outer() {
5 const outerLocal = "outer함수의 지역변수";
6 console.log(outerLocal);
7
8 function inner() {
9 const innerLocal = "inner함수의 지역변수";
10 console.log(innerLocal);
11 console.log(global);
12 }
13 inner();
14 console.log(reference);
15 }
16 outer();
17
18 console.log(global);
위 예시 코드를 콜스택의 동작으로 시각화하면 아래와 같다.

코드가 실행되며 먼저 전역 실행 컨텍스트가 만들어진다. 코드의 가장 바깥 환경이다.
밑으로 코드를 읽어내려가다가 16번째 줄에서 outer 함수가 호출되며, outer 함수의 실행 컨텍스트가 생성된다. (17, 18번째 코드의 실행은 잠시 중단된다!)
✅ 위에서 언급했듯, 실행 컨텍스트는 함수가 선언될 때가 아니라 호출될 때 생성되기 때문에 15번째 줄 전까지는
outer함수의 실행 컨텍스트는 생성되지 않는다.
outer 함수 내부 코드를 실행한다. console.log(outerLocal) 가 실행된다.

내려가다가 13번째 줄에서 inner 함수를 호출했기 때문에 inner 함수의 실행 컨텍스트가 또 위에 쌓인다. 14번째 줄의 실행은 잠시 중단된다.
inner 함수 내부에서 console.log(innerLocal)과 console.log(global)이 실행된다. 실행이 종료되면, inner 컨텍스트는 pop되어 콜 스택에서 사라진다.
이어서 14번째 줄인 console.log(reference) 이 실행된다. outer 함수의 실행도 모두 끝났기 때문에 outer 함수 또한 스택에서 pop된다.
마지막으로 전역 실행 컨텍스트의 console.log(global)이 실행되며, 전역 실행 컨텍스트까지 콜스택에서 제거되면 모든 코드의 실행이 끝난다.
💡 모든 실행이 끝나고 콘솔창을 확인해보면, 해당 순서대로 출력되어있는 것을 볼 수 있을 것이다.

각각의 실행 컨텍스트는 렉시컬 환경(Lexical Environment) 이라는 환경에 바인딩되어 있다.
위에서 실행 컨텍스트의 개념을 실행할 코드에 제공할 환경 정보(변수, 함수, 클래스 등의 이름)들을 모아놓은 객체 라고 정의했는데, 이때 환경 정보들을 모아놓을 수 있는 이유가 바로 이 렉시컬 환경 덕분이다.
1 const global = "전역변수";
2 const reference = "참조";
3
4 function outer() {
5 const outerLocal = "outer함수의 지역변수";
6 console.log(outerLocal);
7
8 function inner() {
9 const innerLocal = "inner함수의 지역변수";
10 console.log(innerLocal);
11 console.log(global);
12 }
13 inner();
14 console.log(reference);
15 }
16 outer();
17
18 console.log(global);
아까 보았던 예시 코드의 각각의 실행 컨텍스트의 렉시컬 환경을 시각화하면 다음과 같다.

전역 렉시컬 환경의 전역 환경 레코드는 객체 환경 레코드와 선언적 환경 레코드로 구성되어 있다.

객체 환경 레코드
객체 환경레코드는 기존의 전역 객체가 관리하던
-var 키워드로 선언한 전역변수
-함수 선언문으로 정의한 전역함수
-빌트인 전역 프로퍼티와 빌트인 전역함수 ( 👉 관련글)
-표준 빌트인 객체를 관리한다. ( 👉 관련글)
선언적 환경 레코드
선언적 환경 레코드는 let, const 키워드로 선언한 전역 변수를 관리한다.
(밑에서 더 자세히)
outer 함수 렉시컬 환경함수 렉시컬 환경은 함수 환경 레코드와 외부 렉시컬 환경 참조로 구성되어 있다.

함수 환경 레코드
해당 함수 스코프에 포함된 식별자를 등록하고 등록된 식별자에 바인딩된 값을 관리하는 저장소다.
outerLocal 이라는 변수가 있으며, 이 변수에는 "outer함수의 지역변수" 라는 값이 바인딩되어있다.inner도 등록된다.외부 렉시컬 환경 참조
상위 스코프의 식별자 정보를 담고 있는 것을 말한다. 이때 상위 스코프란 외부 렉시컬 환경, 즉 해당 실행 컨텍스트를 생성한 소스코드를 포함하는 상위 코드의 렉시컬 환경을 말한다.
outer 함수의 상위 스코프는 당연히 전역 컨텍스트이다. 따라서 outer함수는 전역 컨텍스트의 global, reference 변수들을 참조할 수 있게되고, console.log(reference)를 출력할 수 있게 된다.
inner 함수 렉시컬 환경
함수 환경 레코드
innerLocal 이라는 변수가 있으며, 이 변수에는 "inner함수의 지역변수" 라는 값이 바인딩되어있다.외부 렉시컬 환경 참조
inner 함수는 선언되던 시점에 outer 함수의 내부에 있으므로 outer 함수 스코프가 inner의 상위 스코프가 된다( = 정적 스코프). 따라서 outerLocal 값에 가장 먼저 접근이 가능하다.✅ 정적 스코프(lexical scoping)
자바스크립트에서 어떤 함수의 렉시컬 스코프는 함수의 선언 시점에 정해진다. 자연스럽게 해당 함수의 상위 스코프 또한 함수의 선언 시점에 정해진다. 그리고 어떠한 식별자의 값을 참조해야할 때(변수에 해당하는 값을 찾아야 할 때), 자신의 바로 상위 스코프를 탐색한다.let cat = "zero"; function log() { console.log(cat); } function wrapper() { let cat = "nero"; log(); } wrapper(); //zero위 코드에서
Wrapper함수와log함수 모두 상위 스코프는 전역 컨텍스트이다.log는Wrapper내에서 호출된 것이지, 선언된 것이 아니다. 따라서log함수 내부console.log(cat)을 실행할 때,Wrapper의 cat값이 아닌 전역의 cat값을 확인한다.
inner 함수 내부에서 console.log(global)가 실행될 때를 보자.
inner 함수의 환경 레코드 내부에서는 global 이라는 변수값을 찾을 수 없다. 이때 자바스크립트는 가장 가까운 상위 스코프인 outer 함수의 스코프를 탐색한다. outer 를 탐색했는데 여기에도 global이 없다. 그렇다면 이제 다음 상위 스코프인 전역 컨텍스트로 나가서 탐색을 하고, 이곳에서 global에 할당된 값을 찾아 콘솔 값을 출력하게 된다.
이러한 스코프 체인을 통해 inner 함수 외부 렉시컬 환경 참조에 outerLocal, global, reference 변수 모두가 담겨있을 수 있게 된다.(그림)
✅ 스코프 체인(scope chaning)
자바스크립트에서 이처럼 내부 함수에서 외부 함수를 참조할 수 있는 것을 스코프 체인이라고 한다. 특정 함수가 자신의 환경 레코드 내에서 원하는 값을 찾지 못할 경우, 상위 스코프를 차례로 탐색하고, 원하는 값을 찾으면 더 상위의 스코프로 나가지 않고 탐색을 멈춘다. 이러한 외부 렉시컬 환경에 대한 참조를 통해 단방향 연결리스트 형태의 스코프체인을 구현한다.
스코프에 대한 더 자세한 글 👉 함수의 스코프
만약 위의 예시코드에서 outer 함수의 호출 위치를 위로 올리면 어떻게 될까? 함수가 선언되기 전에 호출했기 때문에 오류가 날 것 같지만 정상적으로 실행된다. 이것은 자바스크립트의 호이스팅 현상 때문이다.
호이스팅이란 변수나 함수를 선언하고 초기화했을 때 선언 부분이 최상단으로 끌어올려지는 현상을 의미한다.
1 const global = "전역변수";
2 const reference = "참조";
3
4 outer(); //호출 위치 변경
5 function outer() {
6 const outerLocal = "outer함수의 지역변수";
7 console.log(outerLocal);
8
9 function inner() {
10 const innerLocal = "inner함수의 지역변수";
11 console.log(innerLocal);
12 console.log(global);
13 }
14 inner();
15 console.log(reference);
16 }
17
18 console.log(global);
자바스크립트 코드가 실행될 때, 가장 먼저 전역 컨텍스트가 콜스택에 담긴다고 했다. 이때 실제로 console.log나 함수 등을 실제로 실행하기 전에, 자바스크립트는 생성단계를 거친다.
생성단계 : 선언문만 실행해서 식별자나 함수를 환경 레코드에 기록하는 과정. 눈에 보이지 않게 선언문이 코드 최상단에 끌어올려진다고 생각하면 된다.
위의 렉시컬 환경 그림을 확대하여 전역 렉시컬 환경만 살펴본다면 다음과 같을 것이다. 생성단계에서 이미 자바스크립트는 호이스팅을 통해 전역 세 가지 정보(outer 함수, global 변수, reference 변수) 를 알고있다.
변수 선언과 함수 선언으로 나누어서 자세히 살펴보자.

global과 reference 변수는 호이스팅되어 선언적 환경 레코드에 저장된다.(그림 참고) (위 코드에선 실제로 최상단에 있긴 하나)
✅ ES6 이전과 이후
ES6 이전var키워드를 사용할 때에는,var키워드로 선언된 변수(식별자)가 객체 환경 레코드에 저장됐었다. 이때var변수는 선언되면 생성단계에서 호이스팅에 의해 끌어올려진 후undefined로 초기화되었다. 따라서console.log(cat); //undefined var cat = "meow";와 같은 코드에서는 에러가 아닌
undefined라는 결과를 볼 수 있었다. 자바스크립트는 이미 초기화를 통해{ cat : undefined }로 저장해두었기 때문!
하지만 ES6 등장 이후 생겨난let과const는 선언되면 선언적 환경 레코드에 저장되며, 생성단계에서 호이스팅되긴 하지만 초기화는 되지 않는다.const cat = "meow"이라는 선언 라인을 만나기 전까진{ cat }이라는 식별자만 존재할 뿐이다.console.log(cat); //Reference Error 어쩌구 const cat = "meow";따라서
var일 때와는 달리reference error가 출력된다. 이렇듯 선언 라인 이전 식별자(여기서는 cat)를 참조할 수 없는 구역을 일시적 사각지대(Temporal Dead Zone) 라고 한다. 이 예시에선console.log(cat)라인이 될 것이다.
💡 선언 vs 초기화
선언 : 메모리 공간을 확보하고 식별자와 연결 ex.{ cat }
초기화 : 식별자에 암묵적으로undefined값 바인딩 ex.{ cat : undefined }
위 예시 코드가 실행되어 전역 컨텍스트가 생길 때, outer 함수는 생성단계에서 호이스팅되어 선언과 동시에 함수 생성까지 완료된다. 그리고 전역 렉시컬 환경의 객체 환경 레코드에 { outer : Function } 형태로 저장된다. (그림 참고)
➡️ ⭐️ 즉, 실제로 이 outer 함수가 호출되고 실행되기 전에 이미 자바스크립트는 이 함수의 존재를 알고있다. 이것이 선언 전에 함수를 호출해도 에러가 나지 않는 이유이다.
✅ 하지만 함수 표현식을 사용하면 선언 이전에 함수를 호출할 수 없다!
위 코드의outer함수를 이렇게 함수 표현식으로 바꿔보자.const global = "전역변수"; const reference = "참조"; outer(); //Error: outer is not a function const outer = () => { ... inner(); console.log(reference); } console.log(global);
outer is not a function이라는 에러를 만나게 된다.
함수를 변수에 담으면, 위에서 봤듯이 생성단계에서 자바스크립트는outer라는 식별자를 환경 레코드에 저장한다. 하지만 ES6에서const는 초기화되지 않는다고 했다. 즉{ outer }라는 식별자만이 존재하는 상황에서 함수를 호출했기에,outer is not a function이라는 에러가 생기는 것이다.
만약var outer = () => {...var키워드로 함수를 선언했다면?undefined is not a function라는 에러 문구를 보게 될 것이다.
📖 정리하자면,
1. 자바스크립트는 콜스택에 실행 컨텍스트를 차곡차곡 쌓으며 상단에 위치한 컨텍스트부터 처리한다.
2. 실행 컨텍스트에는 전역 실행 컨텍스트와 함수 실행 컨텍스트가 있는데, 전역 실행 컨텍스트는 JS코드 실행 시작과 동시에 생성되고, 함수 실행 컨텍스트는 함수가 호출되면 생성된다.
3. 실행 컨텍스트는 렉시컬 환경에 바인딩되어있고, 이 환경 덕분에 변수, 함수, 클래스 등의 정보를 참조할 수 있다.
4. 전역 컨텍스트의 전역 렉시컬 환경에는 함수 선언문으로 정의한 함수, 전역 변수 등의 정보가 포함된다.
5. 함수 컨텍스트의 함수 렉시컬 환경에는 자기 자신 함수 내부의 정보와 + 상위 스코프의 식별자 정보들이 담겨있다.
6. 한 함수의 상위 스코프는 해당 함수의 선언 시점에 정해지며(정적 스코프), 함수가 참조값을 찾을 때에는 상위 스코프를 차례차례 탐색한다(스코프 체인).
7. 자바스크립트에서 전역 컨텍스트가 실행될 때 생성단계라는 것을 거치는데, 이때 변수나 함수의 선언이 최상단으로 끌어올려지는 호이스팅 현상이 발생한다.
8. 함수 선언식으로 선언한 함수는 생성단계 때부터 온전하게 환경 레코드에 저장되기 때문에 선언 전에도 호출이 가능하다.
출처
실행 컨텍스트란 무엇인가요?
실행 컨텍스트
[10분 테코톡] 💙 하루의 실행 컨텍스트
[10분 테코톡] 🍧 엘라의 Scope & Closure
DEEP DIVE 한 장 요약 (실행 컨텍스트)
[Javascript] 전역환경 레코드와 호이스팅. 왜?
let과 const는 호이스팅 될까?