WEB/JavaScript

[JavaScript] 실행 컨텍스트

S0PH1A 2024. 4. 13. 02:58
반응형

실행 컨텍스트의 동작 원리에 대해 간단히 알아봅시다. 실행 컨텍스트는 자바스크립트의 동작 원리를 담고 있는 핵심 개념입니다.  실행 컨텍스트를 이해하게 되면 다음 3가지에 대해 알 수 있게 됩니다.

  1. 호이스팅이 발생하는 이유
  2. 클로저 동작 방식. 스코프
  3. 비동기 처리 방식

 

실행  컨텍스트란?

실행 컨텍스트는 "실행할 코드에 대한 환경 정보들을 모아둔 객체"입니다. 즉, 함수가 실행되는 환경에 대한 정보를 갖고 있다고 생각하면 됩니다. 함수는 변수, 매개변수, 내장 함수, this에 대한 정보를 갖고 있죠? 이 정보를 갖고 있는 객체라고 생각하면 됩니다.

 

실행 컨텍스트 동작 방식

자바스크립트는 싱글 스레드로 되어 있으며, 해야 할 작업들을 하나의 스택에 쌓아 보관합니다. 이 스택을 자바스크립트에서는 콜 스택이라 말합니다. 자바스크립트는 함수의 호출이 발생(실행)하게 되면 콜 스택에 이 함수에 대한 정보를 갖는 객체(=실행 컨텍스트)를 생성하여 푸시(push)합니다. 그리고 함수 실행이 완료가 되면 해당 실행 컨텍스트를 팝(pop)하게 됩니다. 가장 위에 있는 코드부터 실행되기 때문에 전체 코드의 환경과 순서를 보장하게 됩니다. 하지만 이로 인해 블로킹(blocking)이 발생합니다. 블로킹이란 앞 함수가 실행되는 동안 뒤에 다른 함수들을 실행하지 못해 멈춰있는 상태를 말합니다. (동기 방식)

 

실행 컨텍스트에 저장되는 정보

3가지 정보가 저장됩니다.

  1. Variable Environment
  2. Lexical Environment 
  3. this binding

 

Variable Environment

  • 함수에 대한 최초 스냅샷 정보 ( 변경되지 않음 )
  • 현재 컨텍스트 내의 식별자들에 대한 정보, 외부 환경 정보를 갖고 있음.

Lexical Environment ⭐️

  • Variable Environment를 복사해서 생성됨.
  • 변경 사항이 계속 적용됨.
  • 현재 컨텍스트 내의 식별자들에 대한 정보, 외부 환경 정보를 갖고 있음.
이름 저장 정보 키워드
Environment Record 매개 변수의 이름, 함수 선언, 변수명 등 호이스팅
Outer Environment Reference 현재 호출된 함수가 선언될 당시의 Lexical Environment를 참조.
( 부모 정보가 담긴다고 생각하면 쉬움 )
클로저 / 스코프

This binding

  • this 식별자가 바라보는 객체
  • 지정되지 않은 경우 전역 객체(Window)를 가리킴.

 

호이스팅

Lexical Environment의 Environment Record는 변수를 호이스팅 할 때 변수 명만 끌어올리고 할당 과정은 원래 자리에 그대로 둡니다. 따라서 호이스팅이 발생하는 겁니다.

변수의 선언 / 초기화 / 할당
- 선언 : 변수를 생성. 해당 식별자를 등록하여 스코프가 참조할 대상을 만듦.
- 초기화 : 메모리에 변수를 저장하기 위한 공간 확보. 기본값으로 undefined가 할당됨.
- 할당 : = 연산자로 값을 할당.

 

따라서 함수의 선언문은 호이스팅 돼서 함수 선언 전에 호출이 가능하지만, 함수의 표현식은 함수 선언 전에 호출되지 않게 됩니다.

console.log(b);      // undefined
console.log(func);   // f func() { ... }
console.log(func2);  // undefined

var b = bbb;

// 함수의 선언문
function func() {
	console.log("a");
};

// 함수의 표현식
var func2 = function () {
	console.log("b");
}

 

아래와 같이 함수의 선언문은 함수 전체를 끌어올리지만 함수의 표현식은 변수 명만 끌어올리기 때문입니다.

var b;              // 변수는 선언부만 끌어올림
function func() {}  // 함수 선언은 전체를 끌어올림
var func2;

b = bbb;

// 함수의 표현식
func2 = function () {
	console.log("b");
}

 

스코프와 스코프 체인

스코프(Scope)는 식별자에 대한 유효범위를 말합니다. A 함수 내에 선언한 변수, 함수는 A 내에서만 접근 가능할 수 있게 됩니다. A 함수 외부에 선언한 변수, 함수는 외부뿐만 아니라 A 함수 내에서도 접근이 가능합니다. 식별자 유효범위를 안에서부터 바깥으로 차례대로 검색하는 것을 스코프 체인이라고 합니다. 이를 가능하게 하는 것은 LexicalEnvironment의 outerEnvironmentReference 때문입니다.

 

함수가 선언될 당시의 LexicalEnvironment를 참조하고 있는 outerEnvironmentReference는 연결리스트(Linked List) 형태를 띱니다. 예를 들어 A 함수 안에 B 함수안에 C 함수로 구성되어 있다면 C 함수의 outerEnvironmentReference에는 B의 LexicalEnvironment, B의 outerEnvironmentReference에 A의 LexcicalEnvironment를 참조하고 있기 때문에 C함수에서는 가장 가까운 순서대로 B함수 → A 함수로 접근이 가능하게 됩니다. 따라서 여러 스코프에서 동일한 식별자를 선언하는 경우 스코프 체인 상에서 가장 가까운 식별자에만 접근 가능하게 됩니다.

var a = 1;
var outer = function () {
  var inner = function () {
    console.log(a);  // 3
    
    // 호이스팅되어 inner에서는 a 호출 시 전역이 아닌 가장 가까운 스코프안에 있는 이 값을 바라봄
    var a = 3;
  }  // outerEnvironmentReference에 outer 함수의 LexicalEnvironment 저장
  inner();
  console.log(a);  // 1
};  // outerEnvironmentReference에 전역 LexicalEnvironment 저장
outer();
console.log(a);  // 1

 

클로저

[링크]

클로저란 이미 생명주기가 끝난 외부 함수(실행 컨텍스트가 종료된 함수)의 변수를 참조하는 함수를 말합니다. 어떻게 접근이 가능할까요? 바로 LexcialEnvrionment의 outerEnvironmentReference에 외부 함수의 정보가 담겨 있기 때문입니다. outerEnvironmentReference에는 함수가 선언될 당시의 LexicalEnvrionment 가 담긴다고 말했습니다. 따라서 이를 이용해 접근이 가능하게 됩니다.

 

클로저 활용 사례

  1. 이벤트 리스너
  2. 정보 은닉
  3. 부분 적용 함수

메모리 누수

가비지 컬렉터는 어떤 값을 참조하는 변수가 하나라도 있으면 그 값은 수집 대상에 포함하지 않아 메모리상에 계속 남아 있게 됩니다. 외부 함수의 변수를 클로저가 참조하고 있기 때문에 메모리상에 계속 남아 있게 되어 메모리 누수가 발생하게 됩니다. ( 2019년부터는 V8엔진의 경우 내부 함수에서 실행되는 변수를 제외하고는 GC 하도록 되었다고 합니다. )

 

자바스크립트는 강제로 가비지 컬렉터를 수동으로 조작할 수 없습니다. 따라서 식별자에 기본형 데이터 null 또는 undefined를 할당하여 이 식별자는 더 이상 사용하지 않으니 GC가 메모리를 해제하도록 할 수 있습니다.

 

 

 

 

 

 

 


참고

* 코어 자바스크립트 - 정재남

* 모던 자바스크립트 Deep Dive - 이웅모

반응형