렉시컬 환경은 실행 컨택스트에서 키와 값을 갖는 객체 형태의 각각의 스코프를 생성하여 식별자를 키로 등록(환경 레코드)하고, 식별자에 바인딩 된 값을 관리하며 상위 스코프에 대한 참조를 스코프 체인으로 기록(외부 렉시컬 환경 참조)하는 자료구조로서 실행 컨텍스트 스택과 함께 실행 컨텍스트를 구성하는 컴포넌트 입니다.
이러한 렉시컬 환경은 크게 환경 레코드와 외부 렉시컬 환경 참조 두 가지 컴포넌트로 구성됩니다.
여기서 환경 레코드란 스코프에 포함된 식별자를 등록하고 해당 식별자의 바인딩 된 값을 관리하는 곳으로 전역 스코프, 전역 빌트인 , 메서드 등 객체등 검색에 필요한 정보를 제공하는 컴포넌트이며,
외부 렉시컬 환경 참조란 상위 코드의 렉시컬을 첨조하는 컴포넌트이며, 이 참조를 통해 단방량 링크드 리스트인 스코프 체인 검색을 구현합니다.
실행 컨텍스트가 동작되기 시작하면 우선적으로 전역 코드가 평가되기 전 빌트인 전역 프로퍼티와 함수, 표준 빌트인 객체가 추가된 전역 객체가 생성되며, 동작 환경에 따라 클라이언트 사이트의 Web API, 호스트 객체를 포함하기도 합니다. 그 다음으로 해당 소스코드가 로드되면 자바스크립트 엔진이 전역 코드를 평가 하는데, 간략한 생성 순서는 다음과 같습니다.
① 전역 코드에 대한 실행 컨텍스트 프로세스)
- 전역 실행 컨텍스트를 생성한 후 실행 컨텍스트 스택에 적재 (여기서 최상위는 전역 실행 컨텍스트)
- 전역 렉시컬 환경을 생성하고 해당 전역 실행 컨텍스트에 바인딩
- 전역 렉시컬 환경을 구성하는 요소중 환경 레코드를 생성합니다.
- 환경 레코드에는 객체 환경 레코드와 선언적 환경 레코드가 있는데, 객체 환경 레코드에는 var 키워드를 포함한 전역 요소들과 관련된 모든 정보들을 제공하고 생성된 전역 객체에 바인딩을 하고, 선언적 환경 레코드에는 객체 환경 레코드를 제외한 let, const와 같은 정보들을 관리 및 저장하는데 여기에 저장된 변수들은 전역 변수와는 다르게 초기화가 진행되지 않아 호이스팅이 발생되지 않는 TDZ(Temporal Dead Zone)이 발생됩니다. 물론 런타임에는 정상적으로 검색이 가능하고요.
- 전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 전역 객체를 가리키는 this 키워드가 바인딩 됩니다. 그리고 this를 호출하면 전역 환경 레코드의 해당 슬롯에 바인딩 되어있는 전역 객체가 호출됩니다.
- 외부 렉시컬 환경 참조를 생성하고, 현재 평가 중인 소스코드를 포함하는 외부 소스코드의 렉시컬 환경인 상위 스코프를 결정합니다. 물론 전역 코드에서는 해당 환경이 최상위 이므로 null의 값을 가지게 됩니다.
- 전역 코드를 등록하는 전반적인 위의 작업들이 끝나면 런타임이 실행되고, 자바스크립트 엔진은 전역 코드를 순차적으로 실행시키며 식별자가 미리 등록되어 있는지를 환경 레코드에서 검색 및 확인하는 식별자 결정 작업을 실행하는데, 이때 식별자를 검색할 수 없는 경우 외부 렉시컬 참조인 상위 스코프에서 해당 식별자를 참조하고, 이때의 상위 스코프는 현재 존재하지 않으므로(null) 검색에 실패하게 됩니다.
다음은 예제로 알아보는 렉시컬 환경 생성 절차와 실행 컨텍스트 바인딩 절차입니다.
① 예제로 알아보는 실행 컨텍스트 절차) 전역 코드 등록 및 실행 var x = 1; // BindObject 객체에 의해 전역 객체의 프로퍼티인 var x 식별자와 정수 리터럴 1을 등록하고 동시에 undefined 값 x에 할당합니다. 후에 런타임에서 var x를 렉시컬 환경에서 검색 후 1을 할당합니다. const y = 2; // 외부 렉시컬 참조에 의해 값이 전혀 없고 단지 const y 식별자로만 등록되므로 호출이 불가능하며 런타임 때에 비로소 렉시컬 환경에서 검색하고 있을 경우 2를 할당. function reformVars(a){ // BindObject 객체에 의해 전역 객체의 메서드 등록 var x = 3; const y = 4 function addNumbers(b){ const z = 5; console.log(a+b+x+y+z); } addNumbers(10); } reformVars(20); // 함수 호출문이므로 등록을 안하고 런타임때에 해당 코드문을 만날때 해당 함수를 호출 ② 예제로 알아보는 실행 컨텍스트 절차) reformVars 함수 호출시 var x = 1; const y = 2; function reformVars(a){ // 해당 함수에 대한 실행 컨텍스트 작업 시작 var x = 3; // 해당 함수의 지역 변수로서 함수 렉시컬 환경에 등록되고 undefined 할당 후 런타임 때에 서칭 후 3 할당 const y = 4 // 해당 함수의 지역 변수로서 함수 렉시컬 환경에 등록만 되고 런타임 때에 서칭 후 3 할당 function addNumbers(b){ // function addNumbers 함수를 등록 const z = 5; console.log(a+b+x+y+z); } addNumbers(10); // 함수 호출문이므로 등록은 안하고 런타임때 호출 } reformVars(20); // ③ 예제로 알아보는 실행 컨텍스트 절차) addNumbers 함수 호출시 var x = 1; const y = 2; function reformVars(a){ var x = 3; const y = 4 function addNumbers(b){ const z = 5; //상기 설명과 동일 console.log(a+b+x+y+z); // 스코프 체인을 따라 해당 메서드를 검색하는데, 해당 렉시컬 환경에도 없으면 reformVars 렉시컬 환경에서도 검색 후 없으면 스코프 체인상 최상단에 위치한 전역 실행 컨텍스트의 렉시컬 환경이 바인딩 하는 전역 객체의 프로퍼티 요소인 메서드인 console.log 메서드를 검색 후 표현식을 평가하는데, 이때 a는 reformVars의 렉시컬 환경에서, b는 addNumbers 렉시컬 환경에서, x와 y는 addNumbers의 상위 스코프인 reformVars 렉시컬 환경에서, z는 addNumbers의 렉시컬 환경에서 검색후 해당 표현식을 연산처리 하게 됩니다. } addNumbers(10); } reformVars(20);
★ 실행 컨텍스트 스택에 적재되어 있던 특정 컨텍스트에서 더 이상 실행할 코드가 없다면 위의 함수들을 종료시킬텐데, 이때 스택에서 실행 컨텍스가 제거되었다고 해서 렉시컬 환경까지 제거되는 것은 아니며, 해당 렉시컬 환경을 참조하는 외부 객체가 있다면 유지되고, 참조하는 객체가 아무도 없을 때에 가비지 컬렉터에 의해 소멸됩니다.
그리고 이 개념은 후에 등장하게 될 '클로저(closure)'과도 이어지는 내용이기에 렉시컬 환경이 어떻게 등록되고, 실행 컨텍스트 스택에서 검색 절차가 이루어 지는지 대략적으로라도 알고 있으면 클로저를 이해하는데 도움이 될것입니다.