[JavaScript] 실행 컨텍스트

kim unknown·2022년 4월 19일
0

JavaScript

목록 보기
11/22
post-thumbnail

1. 실행 컨텍스트(Execution context)란

실행 컨텍스트(Execution context) : 자바스크립트 코드가 실행되는 환경. 실행 컨텍스트는 코드에서 참조하는 변수, 객체, this 등에 대한 레퍼런스를 담고 있다.

자바스크립트 엔진은 코드 실행 전 실행 컨텍스트를 생성한다.
실행 컨텍스트는 this, 변수 객체(Variable Object), Scope chain 이 3가지 프로퍼티를 가지며, 자바스크립트 엔진은 코드가 없을 경우에도 기본적으로 3가지 프로퍼티를 초기화한다.

  • 변수 객체(Variable Object)는 실행에 필요한 여러 정보들을 담을 객체를 의미하며, 실행 컨텍스트가 생성되면 자바스크립트 엔진이 생성한다.
    Variable Object에 담는 정보는 변수, 매개변수(parameter)와 인수 정보(arguments), 함수 선언(함수 표현식은 제외)이 있다.

  • 스코프 체인(Scope Chain)은 일종의 리스트로서 전역 객체와 중첩된 함수의 스코프의 레퍼런스를 차례로 저장하고 있다. 다시 말해, 스코프 체인은 해당 전역 또는 함수가 참조할 수 있는 변수, 함수 선언 등의 정보를 담고 있는 전역 객체(GO) 또는 활성 객체(AO)의 리스트를 가리킨다.

자바스크립트 엔진은 두 과정에 거쳐 실행 컨텍스트를 생성한다.
1. 생성 단계에서 자바스크립트 엔진은 변수 선언을 읽는다.
2. 실행 단계에서 자바스크립트 엔진은 변수 값을 할당한다.

처음 생성할 때 변수의 값도 함께 할당되는 것이 아니고, 변수만 담아놨다가 해당 변수를 선언하는 코드가 실행되면 그 때 값을 저장한다. 변수와 변수 값이 할당되는 시기가 다르다는 것이다.


2. 전역 실행 컨텍스트(global)

코드 실행 전 가장 먼저 생성되는 컨텍스트를 전역 실행 컨텍스트, 글로벌 컨텍스트 라고 한다.

글로벌 컨텍스트에서 this는 따로 설정되어 있지 않다면 최상위인 window를 가리킨다. 변수 객체는 선언된게 없으므로 빈 객체이다. 스코프 체인도 연결될 스코프가 없기 때문에 빈 객체이다.

이 후에 코드가 실행되게 되면 그 코드에 대한 실행 컨텍스트가 생성되며 스택에 쌓이게 된다. 실행이 완료된 코드에 대한 실행 컨텍스트는 스택에서 지워지며 최종적으로 모두 마치면 글로벌 컨텍스트도 삭제된다.

해당 내용을 그림으로 그려보았다.


3. 함수 실행 컨텍스트

함수가 실행되면, 그에 따라 함수 실행 컨텍스트 가 생성된다. 실행 컨텍스트는 스택에 저장되기 때문에 글로벌 컨텍스트 위에 함수 컨텍스트가 쌓이는 구조가 된다. 이 때 사용되는 변수들은 변수 객체 안에서 값을 찾고, 없다면 스코프 체인을 따라 올라가며 찾는다. 즉, 스코프 체인을 따라 글로벌 환경에 도달하게 되는 것이다.

function Func1() {
  let a = 10
  return a;
  
  function Func2(num) {
    return num;
  }
  
  return Func2(a);
}
Func1()

위 코드를 예시로 함수 실행 컨텍스트 살펴보겠다.
① 가장 먼저 글로벌 컨텍스트가 생성될 것이다. 글로벌 스코프에서는 this는 최상위인 window를 가리킨다.
② Func1 함수가 실행되면 Func1 실행 컨텍스트가 생성되고 스택에서 글로벌 컨텍스트 위에 쌓일 것이다.
③ 이 때 생성된 Func1 컨텍스트의 변수 객체에는 함수 안에 정의된 변수인 a가 들어간다. this는 현재 가리키는 것이 없으므로 undefined가 뜨고, 스코프 체인은 글로벌을 가리킨다.
④ 함수의 실행이 끝나면 Func1 컨텍스트가 제거되고 스코프 체인을 따라 글로벌 컨텍스트로 돌아간다.
⑤ 모든 실행을 마치면 글로벌 컨텍스트도 제거되게 된다.

해당 내용을 그림으로 그려보았다.


• this의 동적 바인딩

함수가 호출되는 환경에 따라 this는 맥락적으로 가리키는 대상이 달라진다. 이를 동적 바인딩(dynamic binding) 이라 한다.

함수가 호출되는 상황은 매우 다양한데 아래와 같다.
• 함수 호출 : 함수를 직접 호출
• 메서드 호출 : 객체의 메서드를 호출
• 생성자 호출 : 생성자 함수를 호출
• 간접 호출 : call, apply 등을 이용하여 함수를 간접 호출
• 콜백 함수 호출

이처럼 일반 함수의 this는 새롭게 생성된 실행 컨텍스트를 가리킨다.
이렇게 동적으로 바뀌는 this를 bind, apply, call 등을 사용하여 직접 조작할 수도 있다.

그런데 this의 조작이 불가능한 경우도 있다. 바로 화살표 함수의 this이다.
화살표 함수의 this는 호출된 함수를 둘러싼 실행 컨텍스트를 가리킨다. 즉, 화살표 함수가 선언될 때의 this, 부모의 컨텍스트를 가리킨다.
따라서 bind, apply, call을 사용해도 this값 조작이 불가능하다.

즉, this값이 변경되는 처리를 해야될 때는 일반 함수로 작성해야 한다.


4. 렉시컬 환경(Lexical Environment)

렉시컬 환경은 실행할 스코프 범위 안에 있는 변수와 함수를 프로퍼티로 저장하는 객체이다. 렉시컬 환경은 실행 컨텍스트 안에 정의된 Variable Object로 이해할 수 있다. 즉, 우리가 소스 코드를 실행하면서 참조가 필요한 변수의 값을 이 렉시컬 환경에서 식별자 이름을 키로 찾는다고 보면 된다.
* Variable Object > Lexical Environment 구조로 생각하면 될 듯 하다.

앞서 말했듯, 자바스크립트 엔진은 생성 단계에서 코드를 읽어 실행 컨텍스트에 저장한다. 이 때, 변수는 실행 컨텍스트의 렉시컬 환경에 저장된다. 변수의 값은 나중에 변수 선언 코드가 실행될 때 저장된다고 했는데, 예외적으로 함수 선언문은 실행 단계에서 함수 전체가 통채로 저장된다.

렉시컬 환경의 저장 방식은 변수나 함수 선언문에 따라 차이가 있어 이에 따라 호이스팅이란 것이 발생하게 된다.


5. 호이스팅(Hoisting)

호이스팅(Hoisting)은 변수 선언문이 마치 호이스팅 최상단에 끌어올려진 듯한 현상이다. 실제로 선언문이 있는 코드라인을 물리적으로 최상단으로 끌어올린 것이 아니라, 자바스크립트 엔진이 먼저 코드 전체를 스캔하면서 변수를 실행 컨텍스트에 미리 기록해두기 때문에 발생하는 것이다.

var, let, const 모두 호이스팅이 발생한다. 하지만 변수 생성 과정의 차이로 인해 값이 할당되기 전에 참조하면 서로 다른 결과를 도출한다.

변수의 생성 단계는 3가지로 나뉜다.
1. 선언 단계
2. 초기화 단계
3. 할당 단계

var는 선언과 초기화가 동시에 진행된다. 이 때 값을 undefined로 초기화하기 때문에 값을 할당하기 전에 참조해도 오류가 나지 않고 undefined 값을 얻는다.

반면 letconst는 선언 단계와 초기화 단계가 분리되어 진행된다. 호이스팅 되면서 선언단계가 이루어지지만, 초기화 단계는 실제 코드에 도달했을 때 진행된다. 따라서, 실제 코드에 도달하기 전에 먼저 참조를 하게 되면 ReferenceError가 발생한다.

이는 Temporary Dead Zone(TDZ) 때문이다. let과 const로 선언된 변수들은 실제 코드에 도달하기 전까지는 TDZ 영역에 존재하며, 접근할 수 없다. TDZ로 인해 잠재적인 버그를 줄일 수 있다.

* 변수 선언 코드에 도달하기 전까지의 영역을 변수에 접근할 수 없다는 의미로 Temporary Dead Zone(TDZ)이라고 한다.

함수 표현식(const func = () => {})은 함수를 변수에 담고 있기 때문에 변수 호이스팅과 동일하게 동작한다.

함수 선언문(function func() {})은 실행 컨텍스트에 함수 전체가 통채로 저장되기 때문에 함수 이름을 key로 하고 함수 자체를 value로 저장하여 완전하게 초기화된다. 따라서, 완전하게 저장된 상태에서 접근하는 것이기 때문에 변수 선언 이전에 변수에 접근하더라도 변수를 참조할 수 있다.


참고 자료
🐰 엘리스 SW 엔지니어 트랙 2기 자바스크립트 심화 수업
🔗 실행 컨텍스트와 자바스크립트의 동작 원리
🔗 실행 컨텍스트
🔗 [JavaScript] 렉시컬 환경

0개의 댓글