page owner's profile image
minsug
for 문의 작동원리를 ECMAScript로 알아보자ECMAScript 시리즈 1
2024년 5월 23일
9분

개요

자바스크립트의 작동 방식을 가장 접근성 있게 설명해놓은 문서는 단연 MDN이지만 ECMAScript만큼 명쾌한 설명을 제공하지 않는다. 그런데 ECMAScript는 문서 내에서 사용하는 단어부터 흐름까지 전혀 사용자친화적이지 않아서 읽기가 여간 어려운 것이 아니다. 그럼에도 ECMAScript를 읽지 않고 자바스크립트를 온전히 이해할 수 없다는 판단 하에 자바스크립트를 ECMAScript를 기준으로 다시 정리하고자 이 시리즈를 시작한다.

for 문이 반복적으로 렉시컬 환경을 생성하는 이유

자바스크립트에서 for 문을 사용해봤다면 실행문 내에서 const 키워드로 생성된 식별자가 반복될때마다 새롭게 할당되는 것을 경험해보았을 것이다. 이는 실행문마다 스코프가 달라 가능한 것인데 for 문은 어떤 원리로 렉시컬 환경을 계속해서 생성할 수 있을까? for 문의 기본부터 하나하나 뜯어보자.

편의를 위해서 다음과 같이 단어를 지칭하겠다.
조건문: for 문에서 ( )에 담기는 표현식
반복문: for 문에서 에 담기는 문

for 문이라고 다 같은 for 문이 아니다

ECMAScript에 따르면 for 문의 조건문에는 3 개의 표현식이 들어갈 수 있는데 이 표현식의 갯수에 따라 작동 방식이 달리진다.

  • 첫 번째 표현식이 존재할 경우 실행한 결과값을 exprRef 변수에 저장한다.
  • 두 번째 표현식이 존재할 경우 이를 test 변수에 저장한다. 만약 존재하지 않는다면 test를 EMPTY라고 정의하며 반복문을 무한히 순회한다.
  • 세 번째 표현식이 존재할 경우이를 increment 변수에 저장한다. 만약 존재하지 않는다면 increment를 EMPTY라고 정의하며 반복문을 무한히 순회한다.

한 가지 재미있는 점은 표현식의 갯수뿐만 아니라 첫 번째 표현식에서 어떤 키워드를 사용하냐에 따라 반복문의 작동 방식이 달라진다는 것이다. 만약 var를 사용한다면 위 작동방식과 같지만 let이나 const를 이용할 경우는 다르다. 다음은 ECMAScript에서 발췌한 이미지로 let이나 const가 사용됐을 때 새로운 환경을 만드는 방식을 설명한다.

NewDeclarativeEnvironment는 새로운 실행 컨텍스트를 생성한다.NewDeclarativeEnvironment는 새로운 실행 컨텍스트를 생성한다.

해석해보면 아래와 같다.

  • 현재 실행 컨텍스트를 oldEnv에 저장한다.
  • oldEnv를 상위 스코프로 참조하는 새로운 실행 컨텍스트를 loopEnv에 저장한다.

즉, for 문이 실행될때 새로운 실행 컨텍스트를 생성하기 때문에 조건문 내 변수가 바깥 스코프와 격리되어 다른 환경을 가진다. 반면에 var는 위와 같은 작동이 없기 때문에 바깥 스코프에서 조건문 내 변수를 참조할 수 있다. 이를 이렇게 증명할 수 있다.

for (let i = 0; i < 3; i++) {
}
 
console.log(i) // ReferenceError: i is not defined
 
for (var j = 0; j < 3; j++) {
}
console.log(j) // 3

let으로 선언된 i는 for 문 내에서만 존재하는 변수이지만 var로 선언된 j는 상위 스코프에 등록된 변수입니다.

이처럼 var와 let을 사용한 for 문은 다르게 작동하는데 그 이유는 본질적으로 var는 함수 스코프를 따르고 let은 블록 스코프를 따르기 때문이다. 다시 말해서, ECMAScript는 함수 스코프와 블록 스코프에 따른 for 문 작동법을 명시한 것이다.

실행문이 렉시컬 환경을 만드는 법

이제 for 문의 본체인 실행문이 어떻게 작동하는지 한번 알아보자.

ECMAScript에서 정의하는 실행문의 작동 순서ECMAScript에서 정의하는 실행문의 작동 순서

뭐가 되게 많은데, 우리의 질문인 “for 문에서 어떻게 순회마다 렉시컬 환경을 생성할까”는 CreatePerIterationEnvironment(이하 CPIE)에서 답변을 찾을 수 있다. CPIE는 순회마다 실행되며 이름처럼 순회환경을 생성한다.

CPIE의 작동 순서, 여기에 답이 있다.CPIE의 작동 순서, 여기에 답이 있다.

perIterationBindings: 각 순회마다 초기화해야할 변수 리스트
lastIterationEnv와 outer: 현재 실행 컨텍스트
thisIterationEnv: outer를 상위 스코프로 갖는 새로운 실행 컨텍스트

a번과 d번에 답이 나와있다. 현재 실행 컨텍스트를 outer 그리고 outer를 외부 환경으로 갖는 새로운 실행 컨텍스트를 생성한다. 즉, 순회할때마다 실행되는 CPIE가 상위 스코프와 분리되는 렉시컬 환경을 만든다.

여기서 실행문의 변수들이 어떻게 관리되는지 조금만 더 들어가보자. CPIE의 내부 동작은 목적에 따라 환경 및 변수 설정, 변수 바인딩 및 초기화 그리고 환경 업데이트로 나눌 수 있다. 이 중 변수 바인딩 및 초기화는 e번에 해당한다.

e번을 해석해보면 다음과 같다.

  1. thisIterationEnv 에서, 주어진 perIterationBindings 리스트에 포함된 각 변수 이름 (bn)에 대하여 변경 가능한 바인딩을 생성합니다.
  2. “변경 가능한 바인딩”이란 변수 값이 실행 중에 변경될 수 있음을 의미하며, let이나 var로 선언된 변수들이 이 범주에 해당합니다. 여기서 false 인자는 이 바인딩이 엄격 모드(strict mode)에서 삭제 불가능함을 나타냅니다.
  3. 각 변수 bn에 대해, 이전 반복에서 사용되었던 렉시컬 환경 lastIterationEnv에서 해당 변수의 값을 조회합니다. 이를 lastValue라고 합니다.
  4. 조회된 lastValue를 사용하여 thisIterationEnv 내의 새로 생성된 바인딩에 해당 값을 초기화합니다.

다시 말해서, 각 반복문은 이전 반복문에서 사용된 변수들의 값을 기억하고 있다.

아는 것과 증명하는 것

경험으로 아는 것과 이를 증명하는 것은 다르다. for 문만 보아도 실제로 엄청나게 많은 절차들을 통해 우리가 아는 반복문을 실행하기 있는 것을 알 수 있다. 어떤 원리를 증명할 수 있다는 것은 틀린 것을 바로 잡을 수 있다는 것을 의미한다.

이미 알고 있는 자바스크립트를 ECMAScript를 통헤 계속해서 증명하고자 한다.

출처

ECMAScript - for statement