본문 바로가기

CS/모던 자바스크립트 Deep Dive

23장 실행 컨텍스트(2)

반응형

2023년 4월 15일 375p~387p

23장 실행 컨텍스트

23.6.4 foo 함수 코드 평가

현재 전역 코드 평가를 통해 전역 실행 컨텍스트가 생성되었고 전역 코드를 실행하고 있다. 현재 foo 함수를 호출하기 직전이다.

var x = 1;
const y = 2;

function foo (a) {
  var x = 3;
  const y = 4;

  function bar (b) {
    const z = 5;
    console.log(a + b + x + y + z);
}
  bar(10);
}

foo(20); // ← 호출 직전

foo 함수가 호출되면 전역 코드의 실행이 일시 중단되고 foo 함수 내부로 코드의 제어권이 이동한다. 그리고 함수 코드를 평가하기 시작한다.

1. 함수 실행 컨텍스트 생성

먼저 foo 함수 실행 컨텍스트를 생성한다. 이 컨텍스트는 함수 렉시컬 환경이 완성된 다음 실행 컨텍스트 스택에 Push된다.

2. 함수 렉시컬 환경 생성

foo 함수 렉시컬 환경을 생성하고 foo 함수 실행 컨텍스트에 바인딩한다.

2-1. 함수 환경 레코드 생성

파라미터, arguments 객체, 함수 내부에서 선언한 지역 변수와 중첩 함수를 등록하고 관리한다.

2-2. this 바인딩

함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩된다. this는 함수 호출 방식에 따라 결정된다. foo 함수는 일반 함수로 호출되었으므로 this는 전역 객체를 가리킨다. 따라서 함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 바인딩되어 있는 객체가 반환된다.

2-3 외부 렉시컬 환경에 대한 참조 결정

외부 렉시컬 환경에 대한 참조에 foo 함수 정의가 평가된 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당된다. foo 함수는 전역 코드에 정의된 전역 함수이므로 전역 코드 평가 시점에 평가된다. 이 시점에 실행 중인 실행 컨텍스트는 전역 실행 컨텍스트이므로 외부 렉시컬 환경에 대한 참조에 전역 렉시컬 환경의 참조가 할당된다.

자바스크립트는 함수를 어디서 호출했는지가 아닌 어디서 정의했는지에 따라 상위 스코프를 결정한다. 그리고 함수 객체는 자신이 정의된 스코프, 즉 상위 스코프를 기억한다. 자바스크립트 엔진은 함수 정의를 평가하여 함수 객체를 생성할 때 현재 실행 중인 실행 컨텍스트의 렉시컬 환경, 즉 함수의 상위 스코프를 함수 객체 내부 슬롯 [[Environmet]]에 저장한다.

함수 렉시컬 환경의 외부 렉시컬 환경에 대한 참조에 할당되는 것은 함수의 상위 스코프를 가리키는 함수 객체 내부 슬롯 [[Environmet]]에 저장된 렉시컬 환경의 참조다. 즉, 함수 객체의 내부 슬롯 [[Environmet]]가 렉시컬 스코프를 구현하는 메커니즘이 되는 것이다.

23.6.5 foo 함수 코드 실행

이제 런타임이 시작되어 foo 함수의 코드가 실행된다. 파라미터에 아규먼트가 할당되고, 변수 할당문이 실행되어 지역변수에 값이 할당되며 bar 함수가 호출된다. 이때 식별자 결정을 위해 실행 중인 실행 컨텍스트의 렉시컬 환경에서 식별자를 검색하기 시작한다. 

23.6.6 bar 함수 코드 평가

현재 foo 함수 코드 평가를 통해 foo 함수 실행 컨텍스트가 생성되었고 foo 함수 코드를 실행하고 있으며 현재 bar 함수를 호출하기 직전이다.

var x = 1;
const y = 2;

function foo (a) {
  var x = 3;
  const y = 4;

  function bar (b) {
    const z = 5;
    console.log(a + b + x + y + z);
  }
  bar(10); // ← 호출 직전
}

foo(20);

23.6.7 bar 함수 코드 실행

런타임이 시작되어 bar 함수의 코드가 실행된다. 파라미터에 아규먼트가 할당되고, 변수 할당문이 실행되어 지역변수에 값이 할당된다.

1. console.log 식별자 검색

먼저 console 식별자를 스코프 체인에서 검색한다. 스코프 체인은 현재 실행 중인 실행 컨텍스트의 렉시컬 환경에서 시작하여 외부 렉시컬 환경에 대한 참조로 이어지는 렉시컬 환경의 연속이다. 전역 렉시컬 환경은 객체 환경 레코드와 선언적 환경 레코드로 구성되어 있다. console 식별자는 객체 환경 레코드의 BindingObject를 통해 전역 객체에서 찾을 수 있다.

2. log 메서드 검색

이제 console 객체에서 log 메서드를 검색한다. log 메서드는 상속된 프로퍼티가 아니라 console 객체가 직접 소유하는 프로퍼티다.

console.hasOwnProperty('log'); // -> true

3. 표현식 a+b+x+y+z의 평가

식별자는 스코프 체인, 즉 현재 실행 중인 실행 컨텍스트의 렉시컬 환경에서 시작하여 외부 렉시컬 환경에 대한 참조로 이어지는 렉시컬 환경의 연속에서 검색한다.

4. console.log 메서드 호출

23.6.8 bar 함수 코드 실행 종료

실행 컨텍스트 스택에서 bar 함수 실행 컨텍스트가 제거되었다고 해서 bar 함수 렉시컬 환경까지 즉시 소멸하는 것은 아니다. 렉시컬 환경은 실행 컨텍스트에 의해 참조되기는 하지만 독립적인 객체다. 객체를 포함한 모든 값은 누군가에 의해 참조되지 않을 대 비로소 가비지 컬렉터에 의해 메모리 공간의 확보가 해제되어 소멸한다.

23.6.9 foo 함수 코드 실행 종료

23.6.10 전역 코드 실행 종료

 

23.7 실행 컨텍스트와 블록 레벨 스코프

var 키워드로 선언한 변수는 오로지 함수의 코드 블록만 지역 스코프로 인정하는 함수 레벨 스코프를 따른다. let, const 키워드로 선언한 변수는 모든 코드 블록(함수,if,for,while,try/catch 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.

let x = 1;

if (true) {
  let x = 10;
  console.log(x); // 10
}

console.log(x); // 1

if 문의 코드 블록이 실행되면 if문의 코드 블록을 위한 블록 레벨 스코프를 생성해야 한다. 이를 위해 선언적 환경 레코드를 갖는 렉시컬 환경을 새롭게 생성하여 기존의 전역 렉시컬 환경을 교체한다. 이때 새롭게 생성된 if문의 코드 블록을 위한 렉시컬 환경의 외부 렉시컬 환경에 대한 참조는 if문이 실행되기 이전의 전역 렉시컬 환경을 가리킨다.

if문 코드 블록의 실행이 종료되면 if문의 코드 블록이 실행되기 이전의 렉시컬 환경으로 돌아간다.

이는 if 문뿐 아니라 블록 레벨 스코프를 생성하는 모든 블록문에 적용된다.

for문의 변수 선언문에 let 키워드를 사용한 for 문은 코드 블록이 반복해서 실행될 때마다 코드 블록을 위한 새로운 렉시컬 환경을 생성한다. 만약 for 문의 코드 블록 내에서 정의된 함수가 있다면 이 함수의 상위 스코프는 for 문의 코드 블록이 생성한 렉시컬 환경이다.

이때 함수의 상위 스코프는 for 문의 코드 블록이 반복해서 실행될 때마다 식별자(for문의 변수 선언문 및 for문의 코드 블록 내에 선언된 지역변수 등)의 값을 유지해야 한다. 이를 위해 for문의 코드 블록이 반복해서 실행될 때마다 독립적인 렉시컬 환경을 생성하여 식별자의 값을 유지한다.

이에 대해서는 다음 장 “클로저”에서 자세히 살펴본다.

반응형

'CS > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글

14장 전역 변수의 문제점  (0) 2023.04.18
13장 스코프  (0) 2023.04.17
23장 실행 컨텍스트(1)  (0) 2023.04.12
22장 this  (0) 2023.04.11
16장 프로퍼티 어트리뷰트  (0) 2023.04.10