JavaScript 엔진은 코드가 실행될 때 "Call Stack" 이라는 컨테이너에 전역 실행 컨텍스트를 담는다.
전역에서 함수가 호출될 경우, 해당 함수의 실행 컨텍스트를 전역 실행 컨텍스트 위에 쌓는다. (Stack)
전역에서 코드가 실행되다가 depth (함수)를 발견하면 해당 depth로 다이브 한다. (함수가 끝날때까지)
그래서 Call Stack에선 가장 최근에 실행된 컨텍스트가 실행된다.
이때 코드 내에서 선언됐던 선언문 (var, let, const, func 등)을 먼저 확인한다.
그리고 이 선언문을 어딘가에 기록하는데, 이 기록되는 공간이 바로 "Environment Record"다.
var
👉 전역 실행 컨텍스트에 일단 선언된 식별자를 기록하고, var
로 선언된 경우, 식별자의 값을 "undefined"로 초기화 해둔다.console.log(tvChannel);
// 2. 선언문을 살펴본 후 처음 만나는 함수일 때, 해당 함수를 먼저 실행한다.
//이때 JS는 식별자가 있다는 사실과 해당 식별자와 바인딩된 값이 undefined로 환경 레코드에 저장되어있기 때문에, "undefined"라는 값을 참조하여 출력한다.
//= 실행단계 (Execution Phase)
var tvChannel = "Netflix";
// 1. 전역 실행 컨텍스트가 처음 call stack에 쌓일때, 환경 레코드에 변수 식별자 tvChnnel의 값이 초기화 (undefined) 상태로 저장된다.
//= 생성단계 (Creation Phase)
// 3. 이때 2번의 console.log() 함수가 종료되면, 자바스크립트는 다시 위에서부터 아래로 읽는다.
// 이때 선언되어있던 식별자의 값을 정확히 확인하고 전역 실행 컨텍스트의 환경 레코드에 값을 업데이트 한다.
// "undefined" => "Netflix"
console.log(tvChannel);
// 4. 다시 2번 단계와 동일한 함수가 선언되었을 때, 자바스크립트는 환경 레코드를 다시 참조한다.
// 이때 이미 3번 단계에서 변수 tvChannel의 값을 업데이트 했기 때문에, 해당 함수 실행시엔 "Neflix"라는 값이 출력된다.
let, const
👉 var로 기록한 경우와는 다르게, 환경 변수에 식별자의 값을 초기화 하지 않는다.
var
의 경우와는 다르게, let, const
로 선언된 식별자는 식별자만 기록한다.함수 표현식
👉 var, const
같은 키워드에 함수를 담는 방식.var
키워드에 담긴 함수의 경우, 선언 단계에서 값이 "undefined"로 지정되어 있기 때문에, 선언문보다 호출이 먼저 일어날 경우 함수의 값이 "undefined"로 인식되어 Type Error가 일어난다.const
키워드에 함수를 담은 경우, 선언 단계에 값이 지정되지 않아서 선언문보다 호출이 먼저 일어날 경우 값을 찾을 수 없기 때문에 "Reference Error" 가 발생한다.함수 선언식
👉 함수 자체에 이름을 부여하고 함수를 실행하는 방식.study();
function study() {
alert (`공부하자!`)
}
let lamp = false;
console.log(lamp)
let lamp = false;
function goTo2F () {
let lamp = true;
console.log(lamp);
}
goTo2F();
1번 라인과 2번 라인에서 JS는 일단 변수 램프의 값과 선언식 함수를 환경 레코드에 기록한다.
그 후 goTo2F라는 함수가 호출되었을 때, JS는 다시 함수의 선언문으로 이동하고, 새로운 실행 컨텍스트가 기존 전역 환경 컨텍스트 위에 새로 쌓인다.
함수 내부엔 동일한 변수인 lamp가 존재하고, 함수 내부에 있는 lamp의 값은 'true'다.
let lamp = false; // 1. 전역 실행 컨텍스트의 영역 (depth 1)
function goTo2F () { // 2. 선언식 함수 기록 / 4. 함수 실행 (depth 2 실행 컨텍스트 생성)
let lamp = true;
function goTo3F() { // 5. depth 2 함수 내부에서 선언식 함수 발견, 기록 / 7. 함수 실행 (depth 3 실행 컨텍스트 생성)
let pet = 'puppy'
console.log(pet) // 8. "pet"을 depth 3 함수 실행 컨텍스트에서 찾을 수 있기 때문에 쉽게 "puppy"라는 값을 리턴.
console.log(lamp) // 9. depth 3 실행 컨텍스트에서 "lamp" 식별자를 찾을 수 없기 때문에 outer를 타고 depth 2로 이동한다.
//10. depth 2에서 "lamp"의 값을 찾았기 때문에 JS는 depth 1의 "lamp"까지 찾을 필요가 없기 때문에 해당 "lamp"의 값은 "true"로 호출되고 depth 3 함수는 종료되고, 실행 컨텍스트 삭제 (depth 2 함수를 이어서 실행)
};
goTo3F(); // 6. 새로운 함수의 호출 (depth 3)
console.log(lamp); //10. depth 2 함수 실행 종료, 실행 컨텍스트 삭제. (전역 실행 컨텍스트를 마저 이어감)
}
goTo2F(); // 3. 새로운 함수의 호출 (depth 2)
보통 이 순서로 코드가 실행되지만, outer를 사용하는 경우는 현재 depth (현재 실행중인 컨텍스트) 내부에서 찾을 수 없는 식별자의 값을 찾기 위해서다.
depth 3 함수에서 출력값이 console.log(pet)
일 때, JS는 무리 없이 "puppy"라는 값을 찾을 수 있다.
만약 여기서 console의 매개변수가 console.log(corona)
였다면, JS는 depth 3 함수에서 해당 식별자를 찾을 수 없기 때문에 outer라는 사다리를 타고 이전 depth의 실행 컨텍스트로 이동한다.
현 예시의 경우, depth 2 함수의 실행 컨텍스트로 이동을 한다.
이동했을 때 식별자를 찾을 수 있다면 해당 식별자의 값을 depth 3에 사용된 식별자의 자리에 할당해서 문제를 해결할 것이다.
하지만 이전 depth의 실행 컨텍스트에서도 원하는 식별자를 찾을 수 없다면, JS는 계속해서 outer를 통해 현재 실행중인 컨텍스트의 바깥 환경 (lexical environment)을 depth별로 돌면서 값을 찾는다.
전역 실행 컨텍스트까지 이동해서도 참조하고자 하는 식별자의 값을 찾을 수 없을 때 JS는 해당 식별자가 존재하지 않음을 깨닫고 Reference Error으로 결론짓는다.
그리고 console.log(lamp)
의 경우, depth 3 함수의 실행 컨텍스트에서 "lamp"를 찾을 수 없기 때문에 outer를 타고 depth 2 함수의 실행 컨텍스트로 이동한다.