[ JavaScript ] 실행 컨텍스트란 무엇인가?

young_pallete·2021년 6월 17일
0

JavaScript

목록 보기
1/5

실행 컨텍스트

  • 자바스크립트를 동작하기 위한 핵심원리

  • 실행 컨텍스트를 이해하면

    - 식별자 관리 방식

    - 호이스팅

    - 클로저 동작 방식

    이벤트 핸들러와 비동기 처리의 동작 방식

을 이해할 수 있다고 한다.

소스코드의 타입

ECMAScript에서는 컨텍스트 생성 과정 및 관리 방법에 따라 실행 컨텍스트를 크게 4가지로 구분한다.

1. 전역코드

2. 함수코드

3. eval코드

4. 모듈코드

여기에 관해서 3, 4번의 경우 동작 방식도 확인해보려 했지만, 이는 제 문서 서칭 능력이 부족하여(...)1, 2번을 위주로 살펴보려 한다.


전반적인 소스코드 실행 과정

먼저 우리의 JS는 소스코드를 크게

  • 평가
  • 실행

의 과정으로 나누어서 소스코드를 접근한다.

소스코드 평가

이미지

여기서는 먼저 선언된 변수 및 함수들을 먼저 실행 처리한다.

예컨대 다음 소스코드를 보자.

var x = 1;

이를 자바스크립트에서는 평가 과정에 진입하면

var x; // 평가 과정

x = 1; // 실행 과정

로 나누게 되고, var x; 부분에서 "변수 x라는 게 이 스코프에서 선언됐다!"라고만 평가한다. 이때 값을 할당하지 않는다. (undefined로 하는데, 이를 초기화라 한다)

따라서 평가 과정에서는 저러한 선언문만 빠르게 순차적으로 검색, 참조하며 선언을 한다.

소스코드 실행

x = 1;

평가에서는 var x; 선언을 실행했으니 이제 이것만 남았다. 소스코드 실행에서는 선언문 외의 문들을 처리해주는 것이다! 결과적으로 스코프 검색을 통해 적절한 식별자를 찾아내어 할당해준다.

> 결과적으로 이러한 과정에서 발생하는 것이 호이스팅*인 것이다!*


실행 컨텍스트 과정

지금까지 우리는 어떤 소스코드가 있다면 평가 -> 실행의 과정을 거친다는 것을 알았다.

일단 지금까지를 점검하자. 다음은 어떻게 처리될까?

const username = "Jaeyoung";

var age = '28';

function getUserInfo(name, age) {

age -= 1

console.log(`${name}: ${age}`)

};

getUserInfo(username, age)

순서는 다음과 같이 예상할 수 있을 것이다.

1. 전역 코드를 평가한다.

일단 선언문들을 먼저 실행시킨다.

const username;

var age;

function getUserInfo(name, age) // <function object>

2. 전역 코드를 실행

이제 값을 할당해줄 차례. 이때 3번에 주목해보자.

const username = "Jaeyoung"; // 1. Jaeyoung이라는 값을 할당

var age = '28'; // 2. 28이라는 값을 할당

function getUserInfo(name, age) {

age -= 1

console.log(`${name}: ${age}`)

}; // 3. 함수 실행 (💡 그렇다면 내부 소스코드는 ?)

getUserInfo(username, age) 4. getUserInfo 함수 호출

생각해 보니, 런타임 시 전역 코드를 실행하면, 갑자기 함수가 나타나면 어떻게 될까?

이럴 때에는 전역 코드 실행을 잠시 중단하고, 코드의 제어권을 getUserInfo로 전달된다.

2-1. 실행 컨텍스트 스택

이제 자바스크립트에서는 실행 컨텍스트를 또 만들게 되는데... 지금은 하나지만, 함수가 굉장히 많다면 실행 컨텍스트의 순서가 복잡해질 것이다.

이러한 복잡함을 순차적으로 해결하기 위해, JS 내부에서는 실행 컨텍스트 스택에 이러한 실행 컨텍스트들을 쌓기 시작한다.

https://user-images.githubusercontent.com/78713176/122331471-deba4180-cf6f-11eb-8e42-bf61f0cc9a6a.png

3. 함수 실행 컨텍스트 평가

그렇다면 또 getUserInfo는 함수를 평가하고

4. 함수 실행 컨텍스트 실행

함수를 한 줄씩 실행하는 것이다!

5. 함수 실행 컨텍스트 종료, 전역 코드로 복귀

이제 console.log까지 끝나면 getUserInfo의 역할은 다 했다. 스택에서 제거한다.

6. 전역 실행 컨텍스트 종료

이제 함수 호출까지 해내고, 이후에는 전역에서 더 할 것이 없으니 스택에서 제거됨으로써 실행 컨텍스트 스택에는 아무것도 없게 되는 것이다.


실행 컨텍스트 세부 과정

사실 여기까지만 해도 함수 컨텍스트의 원리를 어느정도 이해한 상황이지만, 이왕이면 좀 더 욕심을 내보자!

지금부터는 실행 컨텍스트를 구성하는 컴포넌트를 몇 가지 살펴볼 것이다. 이를 이해하면, 내부 코드의 식별자 바인딩 및 스코프 체인을 좀 더 이해할 수 있을 것이다.

렉시컬 환경

  • 식별자와 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록하는 자료 구조
  • 실행 컨텍스트를 구성하는 컴포넌트

즉 렉시컬 환경의 목적은 크게 스코프 체인, 식별자 관리이다.

또 여기서, 렉시컬 환경은 2가지 컴포넌트로 구성된다.

  • **Lexical Environment
  • **Variable Environment

로 이루어져있다.

사실 둘은 원래 같은 Lexical Environment를 참조하는데, strict mode라던지, try/catch문 등의 특정 상황을 만나면 내용이 달라진다고 한다.

  • 두 컴포넌트는 공통적으로 다음과 같은 컴포넌트로 구성된다.

    • **EnvironmentRecord** 식별자 함수를 저장하는 컴포넌트

    • ***OuterLexicalEnvironmentReference*** 외부 환경을 참조하는 컴포넌트

      https://user-images.githubusercontent.com/78713176/122338328-85a3db00-cf7a-11eb-9849-59f0cc5b288d.png

      특이한 게 요놈들이 분리되면, 그중 LexicalEnvironment라는 게 VE를 참조한다.

      (추측건대... 이는 Temporal Dead Zone를 구현하기 위해 별도로 분리해내고 이를 VE에 합치는 원리인 듯 싶다.)

      이러한 예외적 상황이 많기 때문에 다음 예제의 실행 컨텍스트 과정 설명에 있어 그냥 통틀어서 **렉시컬 환경**으로 통일해서 과정을 설명하겠다.


실행 컨텍스트 세부 과정 - 예제

다음 예제는 코어 자바스크립트라는 책에서 갖고 오신 분께서 작성하신 에서 또 갖고 왔다.

그렇다. 직접 만들기 귀찮았다. (...)

var a = 1;

function outer() {

	function inner() {
	
		console.log(a);   // undefined
		
		var a = 3;
	
	}
	
	inner();
	
	console.log(a);  // 1
	
}

outer();

console.log(a);  // 1

위 예제는 다음과 같은 실행 컨텍스트 스택 과정을 만들 것이다.

자, 이제 살펴보자!

https://phrygia.github.io/assets/post/scope.jpg

1. 전역 객체를 생성한다.

  • 흔히 말하는 Window(node.js에서는 Global)객체
  • 전역 코드가 평가되기 이전에 생성
  • 빌트인 전역 프로퍼티 + 빌트인 전역 함수 + Web API + 호스트 객체
  • Object.prototype 상속

2. 전역 코드 평가

2-1. 전역 실행코드 생성

전역 컨텍스트가 활성화 되며 콜스택에 담긴다.

2-2. 전역 렉시컬 환경 생성

다음과 같이 전역 렉시컬 환경이 생성되고 전역 실행 컨텍스트를 바인딩한다.

https://user-images.githubusercontent.com/78713176/122339699-5c844a00-cf7c-11eb-8cfd-4d26d926af38.png

  • 2-2-1. 전역 환경 레코드 생성

    전역 스코프, 빌트인 전역 프로퍼티, 빌트인 전역 함수, 표준 빌트인 객체를 제공

    • 객체 환경 레코드 - var, 함수 선언문
    • 선언적 환경 레코드 - letconst, 함수 표현식

2-3. this 바인딩

  • 전역 환경 레코드 [[GlobalThisValue]] 내부 슬롯에 this 바인딩
  • 사실상 전역 객체가 바인딩.
  • 결과적으로 this는 [[GlobalThisValue]] 내부 슬롯에 바인딩된 객체를 반환.
  • 전역 환경 레코드 / 환경 함수 레코드에만 존재

2-4. OuterLexicalEnvironmentReference

  • 상위 스코프를 참조.
  • 이를 통해 단방향 연결 리스트인 스코프 체인을 형성시킴.
  • 전역의 경우 종점

https://user-images.githubusercontent.com/78713176/122346148-99077400-cf83-11eb-9fb8-183cc0b3502f.png

3. 전역코드 실행

이제 여기서부터는 호출하고, 값을 할당하기 시작한다.

다시 코드를 보자.

var a = 1;

function outer() {

	function inner() {
	
		console.log(a);   // undefined
		
		var a = 3;
	
	}
	
	inner();
	
	console.log(a);  // 1
	
}

outer();

console.log(a);  // 1

일단 a = 1이 실행된다.

그 다음 outer 함수가 호출이 된다. (선언은 이미 평가에서 했으니) 이때, 함수 호출을 만나면 제어권을 함수에게 넘긴다.

4. outer 함수 코드 평가

새로운 함수 실행 컨텍스트를 생성하고, 스택에 쌓은 뒤에는 전역코드를 평가하는 과정과 사실상 비슷한 과정을 거친다. 설명하자면

  • 4-1. 함수 실행 컨텍스트 생성
  • 4-2. 함수 렉시컬 환경 생성 (환경 레코드, this 바인딩, 외부 환경 참조)

을 똑같이 거친다. (중복이니 생략!)

정리하면

  • 여기서도 inner함수 선언문을 객체 환경 레코드에 넣어주겠다.
  • this는 일반 함수로 선언되었기에 전역 객체에 바인딩
  • 외부 환경은 상위 스코프인 전역 스코프를 참

5. outer 함수 코드 실행

inner 함수가 호출된다.

그렇다면 또 실행 컨텍스트 안에 inner함수 실행 컨텍스트가 쌓인다.

6. inner 함수 코드 평가 및 실행

(이제 여기서도 같은 과정을 반복하니, 세부 과정은 더 생략하겠다...)

평가

  • 평가에서는 객체 환경 레코드에서 a가 선언이 된다. (var)
  • 객체 환경 레코드에 있는 식별자는 초기화까지 진행

실행

  • 먼저 스코프 체인을 통해서 console 검색 (Window.console)
  • 이후 a 검색, 이때 a는 초기화만 되어있으므로 undefined 콘솔 출력

7. inner 함수 코드 종료

inner 함수가 종료되면서 실행 컨텍스트 스택에서 pop

이때, 렉시컬 환경은 누군가가 참조하고 있을 때까지 소멸 X

8. outer 함수 코드 종료

9. 전역 코드 실행 종료

헥헥... 고생하셨습니다...


참고자료

profile
People are scared of falling to the bottom but born from there. What they've lost is nth. 😉

0개의 댓글