💡 스택: 출입구가 하나뿐인 깊은 우물 같은 데이터 구조
- 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);
var a = 3;
}
inner(); // -------- (2)
console.log(a);
}
outer(); // ---------- (3)
console.log(a);
👉 실행순서 ('스택'의 개념)
(1) 전역 컨텍스트가 콜 스택에 담긴다
(3)에서 outer 함수가 호출되면 outer 실행 컨텍스트가 콜 스택의 가장 위에 담긴다
(2)에서 inner 함수의 실행 컨텍스트가 콜 스택의 가장 위에 담긴다
즉, 전역 컨텍스트 → outer 실행 컨텍스트 → inner 실행 컨텍스트
순으로 쌓이고, inner 실행 컨텍스트 → outer 실행 컨텍스트 → 전역 컨텍스트
순으로 실행된다. 한 실행 컨텍스트가 실행될 때 그 밑의 컨텍스트의 실행은 일시중단되고, 실행이 끝나면 그 실행 컨텍스트는 콜 스택에서 제거된다.
💡 VariableEnvironment: 실행 시의 최초 스냅샷을 담은 내용
💡 LexicalEnvironment: 위의 내용을 그대로 복사한 내용으로, 코드 진행에 따라 변경된다
→ 이 둘은 각각 environmentRecord(현재 컨텍스트 내의 식별자들에 대한 정보)와 outerEnvironmentReference(외부 환경 정보)를 수집한다
💡 EnvironmentReord: 현재 컨텍스트와 관련된 코드의 식별자 정보들을 저장
- ex) 함수의 매개변수 식별자, 함수 자체, var로 선언된 변수의 식별자
💡 호이스팅(중요!!)
자바스크립트 엔진이 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다고 가정하는 가상의 개념
environmentRecord는 현재 실행될 컨텍스트에 어떤 식별자들이 있는지에만 관심이 있고, 각 식별자에 어떤 값이 할당될 것인지는 관심이 없다. 따라서 변수 "선언"부분만 최상단으로 올리게 된다.
👉 원본 코드
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);
👉 호이스팅을 마친 상태의 코드
function a () {
var x; // -------- 수집 대상 1(매개 변수)의 변수 선언 부분
var x; // -------- 수집 대상 2의 변수 선언 부분
var x; // -------- 수집 대상 3의 변수 선언 부분
x = 1; // -------- 수집 대상 1의 할당 부분
console.log(x); // (1) 1출력
console.log(x); // (2) 1출력
x = 2; // -------- 수집 대상 3의 할당 부분
console.log(x); // (3) 2출력
}
a();
변수는 위처럼 선언부와 할당부를 나누어 선언부만 끌어올리지만, 함수 선언은 "함수 전체"를 끌어올린다.
👉 원본 코드
function a() {
console.log(b); // (1)
var b = 'bbb'; // 수집 대상 1 (변수 선언)
console.log(b); // (2)
function b() {} // 수집 대상 2 (함수 선언)
console.log(b); // (3)
}
a();
👉 호이스팅을 마친 상태의 코드
function a() {
var b; // ------- 수집 대상 1 (변수 선언)
function b() {} // ------ 수집 대상 2 (함수 선언)의 전체
// var b = function b() {} // 함수 선언문을 함수 표현식으로 바꾼 코드
console.log(b); // ------ (1) 함수 b 출력
b = 'bbb'; // ------- 수집 대상 1의 할당 부분
console.log(b); // ------ (2) 'bbb' 출력
console.log(b); // ------ (3) 'bbb' 출력
}
a();
정의부만 존재하고 별도의 할당 명령이 없는 것
function a() {/* ... */}
a(); // 함수명 a가 곧 변수명
❗ 주의할 점
함수 선언문이 코드 상단에 선언되어 있는 상태에서 동일한 이름의 함수 선언문이 코드 하단에 중복해서 선언 될 경우, 호이스팅 과정을 통해 두 함수 선언문이 모두 상단으로 끌어올려지고 가장 마지막의 함수가 코드 전체에 영향을 미치게 된다.
따라서 함수 선언문보다 변수의 선언부만 끌어올리는 함수 표현식이 상대적으로 더 안전하다. (가장 좋은 방법은 함수를 지역변수로 만드는 것!)
정의한 함수를 별도의 변수에 할당하는 것
var b = function () {/* ... */}
b(); // 익명 함수 표현식. 변수명 b가 곧 함수명
var c = function d () {/* ... */} // 기명 함수 표현식
c(); // 실행 OK
d(); // 에러! 외부에는 함수명으로 함수를 호출할 수 없다
💡 스코프(Scope) : 식별자에 대한 유효범위
💡 스코프 체인(Scope Chain) : 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것
💡 스코프 체인 확인 예시 - 크롬
전역변수 : 전역 공간에서 선언한 변수
지역변수 : 함수 내부에서 선언한 변수