본문 바로가기

JavaScript

실행 컨텍스트 작동 원리와 클로저

한 동안 글쓰기에 많이 신경을 못썼다 ㅠㅠ

클로저, 호이스팅, this 바인딩 등 자바스크립트의 여러 개념들을 공부할 때 항상 등장했던 실행 컨텍스트에 대해 정리해보려 한다.

 

실행 컨텍스트(Execution Context)란?

스코프와 식별자, 코드 실행 순서에 대한 정보를 담아 코드 실행 환경을 제공하고, 그 결과를 관리해주는 영역이다.

변수 및 함수 선언을 스코프에 등록하고 체인을 거쳐 식별자를 탐색하거나 코드 실행 순서를 변경하는 등 일련의 환경을 제공하고 그 실행 결과까지 관리하는 영역이라고 할 수 있다.

 

🤔  언제 사용되지?

자바스크립트 엔진은 우리가 쓴 코드를 실행하기 위해 평가실행 두 단계를 거친다. 

 

먼저 평가 과정에서는 코드를 실행하기에 앞서 변수 및 함수 선언문들을 스코프에 등록한다.

이 스코프를 실행 컨텍스트가 관리해주고 실행 단계에서 값을 불러오거나 변경사항이 있을 때 변수나 함수에 정해준 이름(식별자)을 키로 업데이트해준다.

 

 


 

🔎  더 자세히 들여다보자!

실행 컨텍스트는 역할 별로 코드 실행 순서를 관리하는 실행 컨텍스트 스택식별자와 스코프를 관리하는 렉시컬 환경으로 구성된다고 할 수 있다.

 

1. 실행 컨텍스트 스택

실행 컨텍스트 스택에는 실행(대기) 중인 작업 단위가 쌓이고, 이를 이용해 코드 실행 순서를 변경할 수 있다.
즉, 전역 또는 함수 코드를 만날 때마다 스택에 쌓아 코드 실행 제어권을 넘겨주며 관리하는 것이다.

코드 출처 모던 자바스크립트

위에서 짚어본 평가와 실행 단계를 떠올려보자.

1. 먼저 전역 코드 평가 단계에서 x, y, foo를 전역 실행 컨텍스트에 등록하고 실행 단계에서 그 값들을 할당해주며 마지막 줄에서 foo 함수를 호출할 것이다.
2. 여기서 전역 코드는 호출한 foo함수의 실행이 끝날 때까지 마쳐지지 못한다. 따라서 호출한 foo 함수에 실행 제어권을 넘겨주고 이어서 함수 코드 평가 및 실행 단계를 거칠 것이다.
3. 그리고 bar 함수의 평가 및 실행까지 완료되면 스택에서 하나씩 빠져나가며 실행을 완료한다.

 


 

2. 렉시컬 환경

렉시컬 환경은 참조할 값들을 기록해두는 역할을 한다. 전역 코드와 함수 코드의 경우로 나누어 알아보자.

(1) 전역 렉시컬 환경

전역 렉시컬 환경은 전역 실행 컨텍스트가 참조하는 객체로, 레코드와 참조 환경에 대한 정보를 담고 있으며 window 객체로 바인딩된다.

함수 렉시컬 환경과는 다르게 전역 렉시컬 환경의 레코드는 두 가지로 나뉘는데 여기서도 var와 let/const의 차이를 알 수 있다.

•  객체 환경 레코드

window 객체는 전역 코드가 평가되기도 전에 이미 존재하는 전역 객체이고, 함수와 var로 선언한 변수는 BindingObject를 통해 전역 객체의 프로퍼티와 메서드가 된다. 

BindingObject로 연결되기 때문에 그냥 x라고 쓸 수도 있지만 window.x로 쓸 수 있고, 반대로 alert 같은 전역 객체의 프로퍼티를 window 식별자 없이 사용할 수 있다.

 

•  선언적 환경 레코드

반대로 let과 const로 선언된 변수는 window 전역 객체에 종속되지 않고 어떤 블록 내에 갇힌 것 같이 존재하는데, 이것이 선언적 환경 레코드이며 TDZ(Temporal Dead Zone, 일시적 사각지대)로 인해 엄한 데서 참조되거나 꼬이는 것을 방지할 수 있다.

 

•  외부 참조 렉시컬

상위 스코프를 가리키지만 전역 코드는 가장 바깥 소스코드이므로 null이 할당된다.

 

 

(2) 함수 렉시컬 환경

가장 내부 함수인 bar 함수를 예시로 알아보면 아래와 같다.

먼저 this 바인딩에서 알아보았듯 내부 함수의 렉시컬 환경 역시 window 전역 객체를 바인딩한다.

함수 환경 레코드는 arguments 객체매개 변수, 함수 내부에서 선언한 지역 변수와 중첩 함수를 등록하고 관리하며, 외부 함수인 foo를 외부 참조 렉시컬 환경으로 가진다.

 

 

 

(3) 블록 레벨 스코프

그렇다면 let/const 키워드가 스코프로 인정하는 블록 레벨은 어떻게 다루어질까?

블록이 사용된 실행 컨텍스트에서 블록을 위한 렉시컬 환경을 새로 만들고, 블록 내에서는 이 렉시컬 환경을 따른다.

또한, 당연하게도 이 예시에서 블록 렉시컬 환경은 상위 스코프인 전역 렉시컬을 외부 렉시컬 환경으로 참조한다.


 

 

클로저와 렉시컬 환경의 상관관계

클로저는 자신이 선언될 당시의 환경을 기억하는 함수와 그러한 스코프 체인을 유지할 수 있도록 하는 구조이다.

 

자바스크립트의 개념 중 클로저를 이해하기 위해서는 렉시컬 환경을 이해해야 한다.

여기서 기억할 특징은 "실행 컨텍스트가 렉시컬 환경을 통해 값들을 참조하지만, 렉시컬 환경은 스택과는 독립적인 객체라는 것"이다. 

const x = 1;

function outer() {
  const x = 10;
  const inner = function () { console.log(x); };
  return inner;
}

const innerFunc = outer(); // line 9
innerFunc(); // 10

분명 line 9에서 호출된 outer 함수는 inner를 반환하며 실행을 마치지만, innerFunc()의 실행 값은 전역 변수 x가 아닌 outer 함수 스코프 내의 x인 10을 가리킨다.

이것은 outer 함수가 실행 컨텍스트 스택에서는 제거되지만 렉시컬 환경까지 사라지는 것은 아니기 때문이다.

 

렉시컬 환경은 자바스크립트 엔진이 더 이상 사용되지 않는다고 판단할 때 가비지 컬렉터에 의해서 할당된 메모리를 잃으며 제거된다.

 

 

 

'JavaScript' 카테고리의 다른 글

this와 Dynamic binding  (0) 2022.01.24
Object-Oriented Programming과 JavaScript  (0) 2022.01.20