
본 내용은 정재남 님의 코어 자바스크립트 책을 읽고 정리한 내용을 작성했습니다.

동일한 환경에 있는 코드들을 실행할 때, 필요한 환경 정보들을 모아 컨텍스트(객체)를 구성한다.
동일한 환경(=실행 컨텍스트)을 구성하는 방법 :
구성 요소

Variable EnvironmentLexical EnvironmentThisBinding : this 식별자가 바라봐야할 대상(객체)이를 콜 스택 Call Stack 이라는 공간에 쌓아올리면서, 실행할 때 가장 위에 있는 컨텍스트부터 실행하는 식으로 전체 코드의 환경 및 순서를 보장한다.
실행 컨텍스트와 콜 스택 동작 예시
// ------------------- (1)
var a = 1;
function outer() {
function inner() {
console.log(a); // undefined
var a = 3;
}
inner(); // ------------- (2)
console.log(a); // 1
}
outer(); // ------------- (3)
console.log(a); // 1


처음 자바스크립트 코드를 실행하는 순간 (1)전역 컨텍스트가 콜 스택에 담긴다.
전역 컨텍스트 내부의 코드를 순차적(위에서 아래로) 실행하다가 (3)의 outer 함수를 호출한다.
2-1. 자바스크립트 엔진은 outer의 환경 정보를 수집하고, 실행 컨텍스트를 생성한 후 콜 스택에 담는다.
2-2. 콜 스택의 상단에는 outer 실행 컨텍스트가 놓인 상태가 됐으므로 전역 컨텍스트의 진행 흐름은 일시중단하고, 상단의 outer 함수 내부의 코드를 순차적으로 실행하게 된다.
이후 outer 함수 내부의 코드 중 (2)에서 inner 함수를 호출하게 된다.
3-1. 마찬가지로 inner의 환경 정보를 이용해 실행 컨텍스트를 생성하고 콜 스택에 담는다.
3-2. inner 실행 컨텍스트가 가장 상단의 컨텍스트가 되어 코드의 진행 흐름을 가져가게 된다.
inner 함수의 범위까지 실행하고 나면, 함수 실행이 종료되면서 inner 실행 컨텍스트가 콜 스택에서 제거된다.
4-1. 그 아래에 있던 outer 실행 컨텍스트가 코드의 진행 흐름을 다시 가져가면서 이전 중단 지점인 (2)에서 부터 이어서 실행한다.
outer 함수의 범위까지 실행하면, 이 또한 outer 실행 컨텍스트가 콜 스택에서 제거된다.
6-1. 전역 컨텍스트만 남은 상태가 되어, 이전 중단 지점 (3)에서 부터 이어서 실행한다.
전역의 코드까지 모두 실행하고 나면 전역 컨텍스트도 제거된다.
최종적으로 콜 스택이 빈 상태가 되고 프로그램이 종료된다.


💡 전역 실행 컨텍스트의 environment Record 에 대해
전역 실행 컨텍스트의 경우 자바스크립트 구동 환경이 별도로 제공해주는 객체(전역 객체)를 활용해서 컨텍스트를 생성하게 된다. 그래서 전역 객체에는 브라우저의 window, 노드의 global 객체 등이 있다. 이들은 자바스크립트의 호스트 객체로 분류된다.
코드 실행 단계에서 선언하기 전에 그 식별자 값에 접근할 수 있는 원리
- “끌어 올리다” 라는 의미 그대로 자바스크립트 엔진이 마치 “식별자 정보들을 최상단으로 끌어올려 놓은 다음 실제 코드를 실행한다.” 라는 이해하기 쉬운 비유적 표현으로 대체한 개념이다.
- 자바스크립트 엔진이 실제로 식별자들을 끌어올리는 행위를 하는 것은 아니고, 그저 평가 단계를 통해 식별자 정보를 한 번 스크리닝하고 그 정보들을 기억하고 있는 상태이기 때문에 코드 실행 단계에서 선언하기도 전에 식별자 값에 접근할 수 있는 것이다.
매개변수와 변수에 대한 호이스팅 - 원본 코드
아래와 같은 코드가 있다고 할 때, 해당 함수의 코드 평가 방식이 어떻게 일어나는지 알아보자.
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);
매개변수와 변수에 대한 호이스팅(2) - 매개변수를 변수 선언/할당과 같다는 전제로 변환된 코드
- Environment Record 는 현재 a 실행 컨텍스트의 대상 코드 안에 어떤 식별자들이 있는지를 체크하는 것이지, 각 식별자에 어떤 값이 할당되어 들어오는지는 관심이 없기 때문에 할당 과정은 그 자리에 둔다.
function a(x) {
var x = 1; // 수집 대상 1(매개변수 선언)
console.log(x); // (1)
var x; // 수집 대상 2(변수 선언)
console.log(x); // (2)
var x = 2; // 수집 대상 3(변수 선언)
console.log(x); // (3)
}
a(1);
매개변수와 변수에 대한 호이스팅(3) - 호이스팅을 마친 상태
01 function a(x) {
02 var x; // 수집 대상 1의 변수 선언 부분
03 var x; // 수집 대상 2의 변수 선언 부분
04 var x; // 수집 대상 3의 변수 선언 부분
05
06 x = 1; // var x; // 수집 대상 1의 할당 부분
07 console.log(x); // (1)
08 console.log(x); // (2)
09 x = 2; // 수집 대상 3의 할당 부분
10 console.log(x); // (3)
11 }
12 a(1);
💡 arguments에 대해
- 실행 컨텍스트를 생성하는 시점에 만들어지는 정보 중 하나로 지정한 매개변수의 개수에 관계 없이 호출 시 전달한 인자를 모두 저장하는 매개가 되어준다.
- 하지만 이는 유사 배열 객체로 배열처럼 활용하기 위해서는 별도의 작업이 필요로 하고, 함수 내부에서 매개 변수의 값을 바꾸면 arguments 값도 따라 바뀌는데, 이렇게 되면 인자를 모두 저장한 데이터의 개념에서 벗어나기 때문에 ES6에서 사용하는 나머지 파라미터 Rest Parameter 를 이용해 arguments를 온전히 대체할 수 있는 방법이 생겼다.
선언문과 표현식 모두 함수를 새롭게 정의하는 방식으로 호이스팅이 적용되는 부분에서 차이가 있다.
함수 선언문
함수 표현식
함수를 정의하는 세 가지 방식
01 // 함수 선언문 -> 함수명 a가 곧 변수명
02 function a() { ... }
03 a(); // a 함수 실행
04
05 // (익명) 함수 표현식 -> 변수명 b가 곧 함수명
06 var b = function () { ... }
07 b(); // b 함수 실행
08
09 // (기명) 함수 표현식 -> 변수명은 c, 함수명은 d
10 var c = function d() { ... }
11 c(); // c 함수 실행
12 d(); // 에러
12번째 줄 - 기명 함수 표현식의 경우 외부에서는 함수명으로 함수를 호출 할 수 없기 때문에 에러가 발생한다.
함수 선언문과 함수 표현식에 대한 호이스팅 - 원본 코드
- 실행 컨텍스트의 Lexical Environment 에서 Environment Record 의 정보를 수집하는 과정에서 발생하는 호이스팅이 함수 선언문과 함수 표현식에 어떻게 적용되는지 알아보자.
console.log(sum(1, 2));
console.log(multiply(3, 4));
function sum (a, b){ // 함수 선언문 : sum
return a + b;
}
var multiply = function (a, b) { // 함수 표현식 : multiply
return a * b;
}
함수 선언문과 함수 표현식에 대한 호이스팅(2) - 호이스팅을 마친 상태
함수 선언문은 정의부 전체를 호이스팅하는 반면 함수 표현식은 변수 선언부만 호이스팅한다.
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;
};
코드 평가 단계
코드 실행 단계
❗ 함수 선언문의 위험성
- 함수 선언문은 이렇게 호이스팅으로 인해 선언하기도 전에 함수를 호출할 수 있다는 것에 있어 어디서든 함수를 호출 하더라도 에러를 일으키지 프로그램을 동작시킬 수 있다는 장점도 있다.
- 하지만 문맥을 읽어나가는 흐름에 있어서 사람이 이해하기에 부자연스러운 것이고 호이스팅으로 인해 식별자 명에 의한 충돌이 나게 되는 경우 가장 나중에 선언된 것이 이전의 것을 덮어 씌우기 때문에 예상하지 못한 코드의 동작 등이 발생할 수 있다는 문제점이 발생한다.
- 원활한 협업을 위해서는 전역공간의 함수 선언과 동명의 함수로 중복 선언되는 일은 없어야 하며, 함수 표현식을 사용하는 것이 선언문 방식보다 더 코드의 흐름을 이해하기 쉽고 호이스팅으로 생기는 문제를 예방할 수 있다.

스코프 체인 예시
01 var a = 1;
02 var outer = function () {
03 var inner = function () {
04 console.log(a);
05 var a = 3;
06 };
07 inner();
08 console.log(a);
09 };
10 outer();
11 console.log(a);
Environment Record : { a, outer } 식별자를 저장한다.Outer Environment Reference : 전역은 선언 시점이 없으므로 아무것도 담지 않는다.thisBinding : 전역 객체Environment Record : { inner } 식별자를 저장한다.Outer Environment ReferencethisBinding : 전역 객체Environment Record : { a } 식별자를 저장한다.Outer Environment ReferencethisBinding : 전역 객체전역변수
지역변수
thisBinding에는 this로 지정된 객체가 저장된다.실행할 코드에 제공할 환경 정보들을 모아 놓은 객체
실행 컨텍스트 객체는 활성화 되는 시점에 다음과 같은 정보를 수집한다.
생성 초기에는 Variable Environment와 Lexical Environment 가 동일한 내용으로 구성 된다.
Variable Environment와 Lexical Environment 의 구성 요소
스코프
스코프 체인
전역 변수와 지역변수