실행할 코드에 제공할 환경 정보들을 모아놓은 객체
즉 동일한 환경에 있는 코드들을 실행할때 필요한 환경 정보들을 모아 컨텍스트를 구성하고,
이를 콜 스택에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장합니다.
VariableEnvironment : 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경정보 + 선언시점의 LexicalEnvironment의 스냅샷( 즉 변경사항 반영되지않음)LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경사항이 실시간으로 반영ThisBinding: 식별자가 바라봐야할 대상 객체
VariableEnvironment와LexicalEnvironment는 처음에는 같은 내용을 담고 있지만,VariableEnvironment는 변경되지 않는 반면LexicalEnvironment는 실시간으로 변경됩니다.
VariableEnvironment 에 먼저 정보를 담은 다음 그대로 복사해서 LexicalEnvironment 를 생성하고 실행 시 활용합니다.
VariableEnvironment 와 LexicalEnvironment 의 내부에는 다음과 같이 구성되어 있습니다.
VariableEnvironment와 LexicalEnvironment를 분리할까?var만 존재했으며, VariableEnvironment에서 변수(var)와 함수 선언(function)을 관리하면 충분했습니다.let과 const가 도입되면서 블록 스코프 가 추가되었습니다.LexicalEnvironment)이 필요해졌습니다.아래의 예시를 확인하겠습니다.
var와 let/const의 스코프 차이function example() {
console.log(a); // undefined
console.log(b); // ReferenceError
var a = 1;
let b = 2;
}
example();
var는 함수 스코프이고, 호이스팅될 때 undefined로 초기화가됩니다.let은 블록 스코프이고, TDZ(Temporal Dead Zone)가 존재해 접근 시 오류가 발생합니다.var는 VariableEnvironment, let과 const는 LexicalEnvironment에서 관리해야 합니다.TDZ란?
let과const변수는 선언되었지만 초기화되기 전까지 접근할 수 없는 구간으로TDZ라고 합니다.
자바스크립트에서let과const도var처럼 호이스팅되지만 초기화되지 않기 때문에 접근하면ReferenceError가 발생하게 됩니다.
function outer() {
var x = 1; // VariableEnvironment에 저장
let y = 2; // LexicalEnvironment에 저장
if (true) {
let y = 3; // 새로운 LexicalEnvironment 생성(블록 스코프)
console.log(y); // 3
}
console.log(y); // 2 (if 블록 내부의 y와 다름)
}
outer();
if 블록이 실행되면서 새로운 LexicalEnvironment가 생성됩니다.if 블록의 LexicalEnvironment는 제거됩니다.y의 값이 if 블록 안과 밖에서 다르게 유지됩니다.여러 블록 스코프를 동적으로 관리하려면 LexicalEnvironment가 필요합니다.
environmentRecord는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장 되어있습니다
- 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자
- 선언된 함수 자체
- 변수의 식별자
변수정보를 수집하는 과정이 끝나더라도 실행 컨텍스트가 관여할 코드들은 실행되기 전 상태이기 때문에 코드가 실행되기 전에도 불구하고 자바스크립트엔진은 이미 해당 환경의 변수들을 모두 알고있는 것입니다.
즉 자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 코드를 실행 한다라고 봐도 무방한것입니다.
이것이 호이스팅 입니다.
아래와 같이 함수 선언문과 표현문을 표현했을때 호이스팅으로 인해 각각 다르게 동작합니다.
console.log(sum(1,2))
console.log(multiply(3,4))
function sum(a,b){
return a+b;
}
var multiply= function (a,b){
return a*b;
}
호이스팅이 된 상태
var sum = function sum(a,b){
return a+b;
}
var multiply;
}
console.log(sum(1,2)) // 3
console.log(multiply(3,4))// ReferenceError
multiply= function (a,b){
return a*b;
}
다음과 같이 함수 선언문은 자체로 호이스팅이 되지만 함수 표현식 은 변수만 호이스팅이 되어 함수 표현식에서 에러가 나오게 됩니다.
호이스팅이 되어 순서에 상관없이 사용할 수 있어서 편할 수 있다고 생각할 수 있지만 개발은 사람이 하는것 이기 때문에 선언된 후에 호출 할 수 있는 방법이 자연스러울 것 같습니다.
console.log(sum(3,4))
function sum(a,b){
return a+b;
}
var a = sum(1,2)
console.log(a)
...
function sum(a,b){
return a+'+'+b + ' = ' +(a+b);
}
var c = sum(1,4)
console.log(c)
다음과 같이 동일한 함수를 2개 선언해 버리면 호이스팅 때문에
마지막에 할당한 함수, 즉 마지막에 선언된 함수만 실행되어서 문제가 발생할 수 있습니다.
개발은 협업이기때문에 물론 잘 정해서 사용하면 될 수 있지만
이러한 부분을 사전에 차단하여 작성하는것도 중요하다고 생각합니다
식별자에 대한 유효범위
어느 범위에서 변수를 참조할 수 있는지를 결정합니다.
경계 밖에서 선언한 변수는 외부 뿐 아니라 내부에서도 접근이 가능하지만 경계 내부에서 선언한 변수는 내부에서만 접근이 가능합니다 .
outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의LexicalEnvironment를 참조합니다.
- 코드상에서 어떤 변수에 접근하려고 하면
현재 컨텍스트의LexicalEnvironment를탐색해서 발견되면 그값을 반환하고, 못하면outerEnvironmentReference에 담긴LexicalEnvironment를 탐색합니다.- 전역 컨텍스트의
LexicalEnvironment가지 없으면undefined를 반환합니다.
var globalVar = "I am global";
function outer() {
var outerVar = "I am outer";
function inner() {
var innerVar = "I am inner";
console.log(innerVar); // I am inner
console.log(outerVar); // I am outer
console.log(globalVar); // I am global
}
inner();
}
outer();
console.log(outerVar); // ReferenceError
스코프 체인 동작 방식:
inner() 실행 시, 먼저 innerVar를 찾고, 없으면 outerVar를 찾고, 그래도 없으면 globalVar를 찾게됩니다. outerVar는 outer() 내부에서 선언되었으므로 inner()에서 접근 가능하지만, outer() 바깥에서는 접근할 수 없습니다.globalVar는 어디에서든 접근 가능 합니다.