Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Archives
Today
Total
관리 메뉴

성운

[JS] javascript에서 실행 컨텍스트의 Variable Environment, Lexical Environment 에 대한 탐구 본문

javascript

[JS] javascript에서 실행 컨텍스트의 Variable Environment, Lexical Environment 에 대한 탐구

pakxe_ 2025. 1. 24. 08:00

들어가며

실행 컨텍스트는 프론트 개발을 시작하고.. 입문서인 코어 자바스크립트를 읽을 때 처음으로 마주했던 개념입니다. 그때는 처음 보는 개념이라 머릿속에 마땅히 비슷한 개념도 없었고, 개발하면서 자주 마주치는 내용도 아니라서 이런 키워드들이 있구나 하며 넘겼는데요.

얼마 전 우테코안의 모 크루가 재밌는 걸 공부하고 있길래 보니 이런 질문을 제게 하더라구요.

"Lexical EnvironmentVariable Environment의 차이가 뭘까?"

분명 들어본 키워드지만 제대로 답을 하지 못했고 호기심이 발동해 저 질문에 대한 답을 찾아보게 되었습니다.

주의

실행 컨텍스트와 초면이신 독자님은 이 글이 어려우실 수 있습니다.

ecma 스펙을 읽어도 자세히 나와있지 않고, 인터넷에는 with라는 지금은 사용되지 않는 키워드를 사용해 설명하는 글이 예시의 대부분이라 100% 정확한 내용이 아닐 수 있습니다.

Lexical Environment, Variable Environment?

이 두 키워드가 무엇인지부터 살펴보려고 합니다.

자바스크립트가 함수를 마주하면 실행 컨텍스트가 생성됩니다. 그리고 이 실행 컨텍스트 내부에는 아래 항목들이 존재합니다.

  • Lexical Environment
  • Variable Environment
  • (this binding) <- 이건 주제를 많이 벗어나기 때문에 설명 생략

Lexical Environment는 스코프 체인을 구성하기 위해 필요하죠. 그런데 이 Lexical EnvironmentVariable Environment를 복사해 만들어진다고 합니다.

그래서 두 가지 궁금증이 생겼습니다.

  1. Variable Environment가 뭔가?
  2. 왜 복사해서 만드는가?

하나씩 답을 찾아보겠습니다.

1. Variable Environment가 뭔가?

var만 존재하던 시절에는 이 Variable Environment(실제 명칭이 아닐 수 있음)만 존재했습니다. 그리고 이 Variable Environment도 결국 environment이기 때문에 Lexical Environment처럼 EnvironmentRecord 안에 변수들을 저장하고 있는데요.

var, 함수 선언식(function 키워드 써서 함수 만드는 방법)들을 Lexical Environment안의 EnvironmentRecord안에 저장합니다. var의 경우 이때 선언과 초기화가 한 번에 수행되지 않았어도 Variable Environment는 undefined로 값을 저장했습니다.

// [시작 상황] VariableEnvironment.EnvironmentRecord = {a: undefined}
console.log(a); // 출력: undefined
var a; 

a = 1; // VariableEnvironment.EnvironmentRecord = {a: 1}
console.log(a); // 출력: 1

js에서는 undefined도 자료형의 하나이기 때문에 첫번째 콘솔 로그를 실행하면 값이 없다는 에러가 뜨지 않고 undefined를 출력하게 됩니다.

이걸 호이스팅이라고 합니다. Variable Environment안에서는 호이스팅이 됩니다. 값을 넣지도 않았는데도 불구하고 값을 출력하는 라인에서 오류가 안뜨는거죠.

2. 왜 복사해서 만드는가?

이 질문에 대한 답의을 알기 위해선 let, const의 등장 이후를 살펴봐야 합니다.

앞서 보았듯 Variable Environment는 호이스팅이 발생합니다. 그리고 var는 함수 스코프입니다. 이 두 특징 모두 직관적으로 코드를 읽기 어렵게 만드는 문제가 있습니다. 그래서 let, const라는 변수 생성 키워드가 등장하게 되었습니다.

그럼 어떻게 호이스팅을 막고 블록 스코프를 구현할 수 있을까요? Variable Environment만으로는 이 기능들을 구현하기 불가능하다고 합니다. 그럼 왜 불가능할까요?

이 이유에 대해서는 스펙에 나와있지 않아서 정확하게는 모르겠어서 일단 내가 이 javascript언어의 개발자고 let, const를 추가해야하는 상황에 놓여있다고 가정해보았습니다. 그러면 Variable Environment에서 호이스팅을 막기 위해 어떻게 할 수 있을까요? 아마 저라면 "var"키워드를 만났을 때는 그대로 하고, "let", "const"를 만났을 때는 undefined로 초기화하지 않고 넘기도록 구현할 것 같습니다. 그리고 블록 스코프 구현을 위해 함수가 아닌 블록 스코프를 만났으며 이 안에 var변수가 있다면 무시하고 넘어가는(외부 함수 스코프가 이미 var를 수집해갔을 것이기 때문)로직도 구현할 것 같아요.

아마 이렇게 여러 분기문이 추가되어 Variable Environment의 구현 자체가 복잡해지는 것도 포함해서 제가 파악하지 못한 이유들로 인해 새 Environment를 구현하려 한 것 같습니다.

그렇게 Lexical Environment가 등장하게 되었습니다. (Variable Environment는 변동없이 그대로 var를 저장합니다.) 그러면 이제 Variable Environment가 하기 어려운 호이스팅과 블록 스코프에 대한 책임을 이 Environment가 가져가면 되겠습니다.

Lexical Environment는 let, const를 만났을 때 EnvironmentRecord안에 이 값을 넣습니다. 이때 undefined로 넣는게 아니라 이 변수에 값이 할당되기 전이라는 의미의 uninitialized를 넣어 초기화 합니다. (참고. unitialized -> 실제 할당 값으로 바뀌기 까지의 시간차이를 TDZ(Temporal Dead Zone)이라고 합니다)

따라서 어떤 변수에 접근하는데 그 값이 지금 uninitialized라면 "Cannot access 'name' before initialization"라는 오류를 띄우는 것이죠. 이를 통해 호이스팅을 간접적으로 막을 수 있게 되었습니다.

// [시작 상황] LexicalEnvironment.EnvironmentRecord = {a: uninitialized}
console.log(a); // ReferenceError: Cannot access 'a' before initialization
let a; 

a = 1; // LexicalEnvironment.EnvironmentRecord = {a: 1}
console.log(a); // 출력: 1

블록 스코프는 블록을을 만났을 때마다 Lexical Environment를 만들어 환경을 격리시키는 식으로 구현됩니다.

이렇게 호이스팅과 블럭 스코프를 Lexical Environment로 구현할 수 있었습니다.

그렇다면 또 궁금한게, 두 개의 Environment가 사용되는 상황에서 var, let이 혼용되어 코드에 작성되어 있을 경우 각 Environment의 EnvironmentRecord에는 어떤 값들이 저장될까요? 보통 var는 Variable Environment, let과 const는 Lexical Environment에 저장되는 걸로 기억하는데.. 정말 그럴까요?

이는 Environment의 내부 값인 EnvironmentRecord, outer중 outer에 들어가는 값에 대해서 먼저 알아야 답을 할 수 있습니다. outer는 스코프 체인을 구현하기 위해 Environment가 저장되는데요. 이 Environment는 Lexical Environment로만 한정됩니다.

그럼 왜 Variable Environment는 outer에 저장될 수 없을까요?
이유는 Variable Environment를 outer에 저장할 경우 let, const로 선언된 변수를 스코프 체인을 통해 찾아낼 수 없기 때문입니다. let, const를 신경쓰면 Variable Environment 안에 분기 처리가 필요하기 때문에 이를 피하기 위해 Lexical Environment가 생겨나게 되었다고 앞에서 말씀드렸었습니다. 결국 Variable Environment는 "var"만 저장하기 때문에 let, const로 선언된 변수를 찾을 수 없습니다. 그렇기 때문에 outer에 Variable Environment를 저장할 수 없게 됩니다.

Lexical Environment를 outer에 저장할 경우 let, const로 선언된 변수는 찾을 수 있다는 걸 압니다. 그 일을 하기 위해 탄생한 환경이니까요. Lexical Environment는 EnvironmentRecord에 let, const로 선언된 변수들을 저장합니다.

outer를 사용하는 이유는 스코프 체인을 구현하기 위함입니다. 그리고 이 outer에는 Lexical Environment만 저장됩니다. 근데 이 Lexical Environment는 let, const를 저장하는 환경이라고 했는데요. 그렇다면 var로 선언된 변수는 어떻게 찾을 수 있는걸까요? let, const만 저장하는 Lexical Environment로만 스코프 체인이 구성된다면 var는 찾지 못하는거 아닌가요?

질문을 잘 생각해보면 답이 나옵니다. var로 선언된 변수를 저장하는 Variable Environment복사해 Lexicla Environment를 만든다고 했습니다. 결국 Lexical Environment가 생성된 직후의 Record안에는 .... var가 있게됩니다. var로 선언된 변수도 스코프 체인에 포함시켜 찾을 수 있도록 하기 위해 Variable Environment를 복사해 Lexical Environment를 만드는 것입니다.

지금까지는 함수 스코프를 만났을 때의 Variable Environment, Lexical Environment를 살펴보았습니다.

그러면 또 궁금한게 생기는데요.
블록 스코프를 만났을 때도 Variable Environment를 복사해 Lexical Environment를 만들까요?

블록 스코프를 만났을 때도 Variable Environment를 복사해 Lexical Environment를 만들까?

답은 '복사하지 않고 새로 Lexical Environment를 만든다' 입니다. 이유는 위에서 말한 복사를 해야하는 이유를 곱씹어보면 금방 알 수 있습니다.

스코프 체인을 타고 var, let, const 키워드로 선언된 모든 변수를 다 접근할 수 있도록 복사해서 Lexical Environment를 만든다고 했습니다. 그렇기에 블록 스코프일때는 복사 절차가 필요 없다는 것입니다.

예를 들어 생각해보겠습니다. foo함수가 있고 그 안에 if문이 있습니다.

function foo() {
  let a = 1;
  var b = 2;

  if() {
      let c = 3;
    // ...   
  }
}

foo()

처음 foo함수를 호출하면 foo 실행 컨텍스트가 생성됩니다. 그러면 foo의 Lexical EnvironmentVariable Environment가 생성되겠죠. 그리고 순서대로 코드를 읽다가 if문을 만납니다. 그러면 if문의 Lexical Environment가 생성됩니다(블록 스코프를 만날 경우 실행 컨텍스트가 아닌 Lexical Environment만 만들어집니다.). 만들어진 if문의 Lexical Environment안의 outer는 무엇을 가리킬까요?

답은 foo함수의 Lexical Environment입니다. var 변수에 대한 정보는 이 foo함수의 Lexical Environment.EnvironmentRecord안에 이미 저장되어있기 때문에 블록 스코프를 만나 생성된 if문의 Lexical Environment는 var를 놓치지 않기 위해 필요한 과정인 '복사 절차'를 밟을 필요가 없는 것입니다.

그래서 처음 질문에 대한 답은?

"Lexical EnvironmentVariable Environment의 차이가 뭘까?"

Variable Environment는 var로 선언된 변수를 저장하기 위한 환경입니다.
그리고 Lexical Environment는 이런 Variable Environment를 복사해서 만들어지는 환경이며 var, let, const로 선언된 변수를 모두 저장할 수 있습니다.

원래는 var 키워드만 존재했기 때문에 Variable Environment만 있어도 충분했으나, 직관적으로 코드를 읽기 위한 let, const 키워드가 추가되었고 이 키워드들이 수행해야하는 기능인 호이스팅 방지, 블록 스코프를 위해 Lexical Environment라는 환경이 등장하게 되었습니다.

참고 자료

http://dmitrysoshnikov.com/ecmascript/es5-chapter-3-2-lexical-environments-ecmascript-implementation/

https://262.ecma-international.org/5.1/#sec-10.3

https://m.blog.naver.com/dlaxodud2388/222655214381

https://seholee.com/blog/varaible-environment-vs-lexical-environment/




읽어주셔서 감사합니다.

글을 읽으며 이해가 어려웠던 부분이나 질문하고 싶은 내용이 있으시다면 이메일또는 댓글 남겨주시길 바랍니다.