자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에
선언된 변수를 위로 끌어올리고 (호이스팅 hoisting),
외부 환경 정보를 구성하고,
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 함수 호출 -> outer 함수 실행 컨택스트 콜 스택에 담김
자바스크립트 엔진은 outer에 대한 환경 정보 수집해서 outer 실행 컨텍스트 생성한 후 콜 스택에 담음
콜 스택의 맨 위에 outer 실행 컨텍스트가 놓인 상태가 됐으므로
전역 컨텍스트와 관련된 코드 실행 일시중단
outer 실행 컨텍스트와 관련된 코드 === outer 내부 코드 순차 실행
(2) inner 함수 호출 -> inner 함수 실행 컨텍스트 콜 스택 가장 위에 담김
outer 컨텍스트와 관련된 코드 실행 일시중단
inner 함수 내부 코드 순차 진행
전역 컨텍스트 vs. 일반적인 실행 컨텍스트
전역 컨텍스트가 관여하는 대상 = arguments 없음 (함수가 아닌 전역 공간)
전역 공간을 둘러싼 외부 스코프가 존재할 수 없기 때문에 스코프체인 상에는 전역 스코프 하나만 존재
(2) inner 함수 중단 시점 후 부터 실행 후 종료 -> inner 실행 컨텍스트 콜 스택에서 제거
(3) outer 함수 중단 시점 후 부터 실행 후 종료 -> outer 실행 컨텍스트 콜 스택에서 제거
(1) 전역 컨텍스트 중단 시점 후 부터 실행 후 종료 -> 전역 컨텍스트 콜 스택 에서 제거
=> 어떤 실행 컨텍스트가 활성화될 때
자바스크립트 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는 데
필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장함
활성화된 실행 컨텍스트의 수집 정보
: 자바스크립트 엔진이 활용할 목적으로 생성할 뿐 개발자가 코드를 통해 확인 불가능

:현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보
선언 시점의 LexicalEnvironment의 스냅샷으로, 변경 사항은 반영되지 않음
실행 컨텍스트를 생성할 때 VariableEnvironment에 정보 먼저 담고
그대로 복사해서 LexicalEnvironment 만들고
이후 LexicalEnvironment 주로 활용
: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨
컨텍스트 구성 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 사전적인 환경
현재 컨텍스트의 내부에는 a, b, c와 같은 식별자들이 있고
그 외부 정보는 D를 참조하도록 구성돼있다.
자바스크립트 엔진이 코드를 실행할 때 사용하는 데이터 구조
현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨
매개변수 이름, 함수 선언, 변수명 등 담김
컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우
그 함수 자체, var로 선언된 변수의 식별자 등이 식별자에 해당
컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집
전역 실행 컨텍스트는 변수 객체를 생성하는 대신
전역 객체 global object 활용
- 변수 정보 수집 과정을 쉽게 이해하기 위한 가상의 개념 (끌어올리다)
매개변수와 변수에 대한 호이스팅 (1) - 원본 코드
function a (x) { // 수집 대상 1 (매개변수)
console.log(x); // (1) // 1 (호이스팅 공부 전 예상)
var x; // 수집 대상 2 (변수 선언)
console.log(x); // (2) // undefined (호이스팅 공부 전 예상)
var x = 2; // 수집 대상 3 (변수 선언)
console.log(x); // (3) // 2 (호이스팅 공부 전 예상)
}
a(1);
매개변수와 변수에 대한 호이스팅 (2) - 매개변수를 변수 선언/할당과 같다고 간주해서 변환한 상태
arguments에 전달된 인자 담는 것 제외
코드 내부에서 변수를 선언한 것과 다른 점 없음, LexicalEnvironment 완전히 같음
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);
매개변수와 변수에 대한 호이스팅 (3) - 호이스팅 마친 상태
호이스팅 처리 --->
environmentRecord는 현재 실행될 컨텍스트의 코드 내에 어떤 식별자들이 있는지 관심 있음 (어떤 값인지는 관심 없음)
변수명만 관심사에 맞춰 순서대로 끌어올리고 할당 과정과 매개변수는 원래 자리에 놔둠
function a () {
var x; // 수집 대상 1의 변수 선언 부분
// 변수 x 선언, 메모리에서는 저장할 공간 미리 확보, 확보한 공간의 주솟값을 변수 x에 연결
var x; // 수집 대상 2의 변수 선언 부분
// 다시 변수 x 선언, 이미 선언된 변수 x 있으므로 무시
var x; // 수집 대상 3의 변수 선언 부분
// 다시 변수 x 선언, 이미 선언된 변수 x 있으므로 무시
x = 1; // 수집 대상 1의 할당 부분
// x에 1 할당, 숫자 1을 별도 메모리에 담고, x와 연결된 메모리 공간에 숫자 1을 가리키는 주솟값 입력
console.log(x); // (1) // 1
console.log(x); // (2) // 1
x = 2; // 수집 대상 3의 할당 부분
// x에 2 할당, 숫자 2를 별도 메모리에 담고, 그 주솟값 든 채로 x와 연결된 메모리 공간으로 가면
// 숫자 1을 가리키는 주솟값이 들어있는데, 2의 주솟값으로 대치
// 변수 x는 숫자 2 가리키게 됨
console.log(x); // (3) // 2
// 함수 내부의 모든 코드가 실행 -> 실행 컨텍스트가 콜 스택에서 제거됨
}
a(1);
함수 선언의 호이스팅 (1) - 원본 코드
function a () {
console.log(b); // (1) // undefined (호이스팅 고려하지 않은 예상)
var b = 'bbb'; // 수집 대상 1 (변수 선언)
console.log(b); // (2) // 'bbb' (호이스팅 고려하지 않은 예상)
function b () { } // 수집 대상 2 (함수 선언)
console.log(b); // (3) // b 함수 (호이스팅 고려하지 않은 예상)
}
a();
함수 선언의 호이스팅 (2) - 호이스팅을 마친 상태
a 함수 실행하는 순간 a 함수 실행 컨텍스트 생성
변수명과 함수 선언 정보 수집 === 순서대로 위로 끌어올림
function a () {
var b; // 수집 대상 1. 변수는 선언부만 끌어올림
function b () { } // 수집 대상 2. 함수 선언은 전체 끌어올림
console.log(b); // (1)
b = 'bbb'; // 변수 할당부는 원래 자리에 남겨둠
console.log(b); // (2)
console.log(b); // (3)
}
a();
함수 선언의 호이스팅 (3) - 함수 선언문을 함수 표현식으로 바꾼 코드
호이스팅이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있음
함수 선언 위치와 무관하게 함수 실행 가능하지만 오히려 혼란을 유발할 수도?!
a 함수 실행하는 순간 a 함수 실행 컨텍스트 생성
변수명과 함수 선언 정보 수집 === 순서대로 위로 끌어올림
function a () {
var b; // 변수 b 선언, 메모리에서는 저장 공간 미리 확보, 확보한 공간의 주솟값 변수 b에 연결
var b = function b () { } // 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있음
// 다시 변수 b 선언, 함수 b를 선언된 변수 b에 할당, 이미 선언된 변수 b 있으므로 선언 과정 무시
// 함수는 별도의 메모리에 담김, 그 함수가 저장된 주솟값을 b와 연결된 공간에 저장
// 변수 b는 함수 가리킴
console.log(b); // (1) 함수 b 출력
b = 'bbb'; // 변수 b에 'bbb' 할당, b와 연결된 메모리 공간에 함수가 저장된 주솟값 담겨있음
// -> 문자열 'bbb' 담긴 주솟값으로 덮어씀
// 변수 b는 문자열 'bbb' 가리킴
console.log(b); // (2) 'bbb'
console.log(b); // (3) 'bbb'
}
a();
// 함수 내부의 모든 코드 실행됐으므로 실행 컨텍스트가 콜 스택에서 제거됨
함수를 새롭게 정의할 떄 쓰이는 방식
함수 선언문 function declaration
function a () { /* ... */ } // 함수명 a == 변수명
함수 표현식 function expression
var b = function () { /* ... */ } // 변수명 b == 함수명var c = function d () { /* ... */ }
c(); // 변수명 // 실행 OK
d(); // 함수명 // 에러! 외부에서 함수명으로 함수 호출 불가능, 오직 내부에서만 접근 가능함수 선언문과 함수 표현식 (1) - 원본 코드
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;
}
함수 선언문과 함수 표현식 (2) - 호이스팅을 마친 상태
var sum = function sum(a, b) { // 함수 선언문은 전체를 호이스팅, 함수도 하나의 값으로 취급
return a + b;
};
// 메모리 공간 확보, 확보된 공간의 주솟값을 변수 sum에 연결
var multiply; // 변수는 선언문만 끌어올림
// 또다른 메모리 공간 확보, 그 공간의 주솟값을 변수 multiply에 연결
// sum 함수를 또 다른 메모리 공간에 저장, 그 주솟값을 선언한 변수 sum의 공간에 할당
// 변수 sum은 함수 sum 바라보게 됨
console.log(sum(1, 2)); // sum 실행 // 3
console.log(multiply(3, 4)); // multiply에 값 할당X // 비어있는 대상을 함수로 여겨 명령
// 'multifly is not a function' 에러 메세지 출력 // 런타임 종료
multiply = function (a, b) { // 함수 표현식 // 변수의 할당부는 원래 자리에 남겨둠
return a * b;
}
함수 선언문의 위험성
전역 컨텍스트가 활성화될 때 전역공간에 선언된 함수들이 모두 위로 끌어올려짐
override : 동일한 변수명에 서로 다른 값을 할당할 경우 나중에 할당한 값이 먼저 할당한 값을 덮어씌움
console.log(sum(3, 4));
function sum (x, y) {
return x + y; // 숫자 반환
}
var a = sum(1, 2);
function sum (x, y) { // 실제 호출되는 함수 === 마지막에 할당한, 선언된 함수
return z + ' + ' + y + ' = ' + (x + y); // 문자열 반환
}
// sum 함수 활용히는 다른 함수에서도 숫자 대신 문자열 넘겨받았어도
// 암묵적 형변환에 따라 오류 없이 통과
var c = sum(1, 2);
console.log(c);
상대적으로 함수 표현식이 안전하다
전역공간에 함수를 선언하거나 같은 이름의 함수를 중복 선언하는 경우는 없어야 함
만약에 전역공간에 같은 이름 함수가 여러개 존재해도 모든 함수가 함수 표현식으로 정의되어 있다면 같은 변수에 다른 주솟값을 할당
console.log(sum(3, 4)); // Uncaught Type Error: sum is not a function
var sum = function (x, y) {
return x + y;
};
var a = sum(1, 2);
var sum = function (x, y) {
return z + ' + ' + y + ' = ' + (x + y); // 문자열 반환
}
var c = sum(1, 2);
console.log(c);
스코프 scope : 식별자에 대한 유효범위
ES5 : 전역공간 제외하면 오직 함수에 의해서만 스코프 생성
ES6 : 블록에 의해서도 스코프 경계 발생 -> 함수 스코프, 블록 스코프
스코프 체인 scope chain : 스코프 안에서부터 바깥으로 차례대로 검색해나감
outerEnvironmentReference : LexicalEnvironment의 두 번째 수집 자료
현재 호출된 함수가 선언될 당시(과거 시점)의 LexicalEnvironment를 참조
연결리스트 linked list 형태
선언 시점의 LexicalEnvironment를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnvironment 있음
outerEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvrionment만 참조하고 있으므로
가장 가까운 요소부터 차례대로만 접근 가능
여러 스코프에서 동일한 식별자를 선언한 경우 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능
스코프 체인
// [ 0 ] 전역 컨텍스트 활성화
// [ 0 ] 전역 컨텍스트의 environmentRecord에 { a, outer } 식별자 저장
// [ 0 ] 전역 컨텍스트는 선언 시점 없으므로
// [ 0 ] 전역 컨텍스트의 outerEnvironment-Reference에는 아무것도 담기지 않음 (this: 전역객체)
var a = 1; // ( 1 ) 전역 스코프에 있는 변수 a에 1 할당
var outer = function () { // ( 2 ) outer에 함수 할당
// ( 4 ) outer 실행 컨텍스트의 environmentRecord에 { inner } 식별자 저장
// ( 4 ) outerEnvironmentReference에는 outer 함수가 선언될 당시의 LexicalEnvironment 담김
// ( 4 ) outer 함수는 전역 공간에서 선언 -> 전역 컨텍스트의 LexicalEnvironment 참조 복사
// ( 4 ) [ GLOBAL, { a, outer } ]
// ( 4 ) 실행 컨텍스트의 이름, environmentRecord의 객체 (this: 전역 객체)
var inner = function () { // ( 5 ) outer 스코프에 있는 변수 inner에 함수 할당
// ( 8 ) inner 실행 컨텍스트의 environmentRecord에 { a } 식별자 저장
// ( 8 ) outerEnvironmentReference에 inner 함수가 선언될 당시의 LexicalEnvironment가 담김
// ( 8 ) outer 함수 내부에 inner 함수 선언되었으므로 outer 함수의 LexicalEnvironment인
// ( 8 ) [ outer, { inner } ] 참조 복사 (this : 전역 객체)
console.log(a); // ( 9 ) 식별자 a에 접근 // undefined
// ( 9 ) inner 컨텍스트 environmentRecord에서 a 검색 -> a 발견, 아직 할당된 값 없음
var a = 3; // ( 10 ) 변수 a에 3할당
// ( 11 ) inner 함수 실행 종료 -> inner 실행 컨텍스트가 콜 스택에서 제거, outer 실행 컨텍스트 재활성화
};
inner(); // ( 6 ) inner 함수 호출
// ( 7 ) outer 실행 컨텍스트 코드 임시중단, inner 실행 컨텍스트 활성화 -> 3번째 줄 이동
console.log(a); // ( 12 ) 식별자 a에 접근 // 1
// ( 12 ) 활성화된 실행 컨텍스트의 LexicalEnvironment에 접근
// ( 12 ) 첫요소인 environmentRecord에서 a 검색
// ( 12 ) 없으면 outerEnvironmentReference에 있는 environmentRecord에서 a 검색
// ( 12 ) 전역 LexicalEnvironment의 a 값 1 출력
};
outer(); // ( 3 ) outer 함수 호출
// ( 3 ) 전역 컨텍스트 코드 임시중단, outer 실행 컨텏스트 활성화 -> 2번째 줄 이동
// ( 13 ) outer 함수 종료 -> outer 실행 컨텍스트 콜 스택에서 제거, 전역 컨텍스트 재활성화
console.log(a); // ( 14 ) 식별자 a 접근
// [ 14 ] 활성화 상태인 전역 컨텍스트의 environmentRecord에서 a 검색, 1 출력
// [ 15 ] 전역 컨텍스트 코드 종료 -> 전역 컨텍스트 콜 스택에서 제거

전역 컨텍스트 -> outer 컨텍스트 -> inner 컨텍스트
규모는 점점 작아지지만 스포크 체인 타고 접근 가능한 변수 수 늘어남
변수 은닉화 variable shadowing
개발자 도구의 콘솔 통한 상위 스코프 정보 확인
현재 실행 컨텍스트 제외한 상위 스코프 정보 확인 가능
함수 내부에서 함수 출력
var a = 1;
var outer = function () {
var b = 2;
var inner = function () {
console.dir(inner);
};
inner();
};
outer();var a = 1;
var outer = function () {
var b = 2;
var inner = function () {
console.log(b);
console.dir(inner);
};
inner();
};
outer();var a = 1;
var outer = function () {
var b = 2;
var inner = function () {
console.log(b);
debugger;
};
inner();
};
outer();전역 변수와 지역 변수
전역 스코프에서 선언한 a, outerouter 함수 내부에서 선언한 inner , inner 함수 내부에서 선언한 a: this 식별자가 바라봐야 할 대상 객체