[REAL Deep Dive into JS] 23. 실행 컨텍스트

young_pallete·2022년 9월 23일
0

REAL JavaScript Deep Dive

목록 보기
23/46
post-custom-banner

🚦 본론

실행 컨텍스트의 정의 및 목적

저는 어떤 것을 이해할 때는 이게 왜 필요한지를 고민해요.
사실, 이게 정말 굳이 필요가 없다면 당장 공부할 필요가 없으니까요.
그래서 살펴봤는데요! ECMAScript의 최신 문서를 보면 다음과 같이 실행 컨텍스트가 정의되어 있습니다.

An execution context is a specification device that is used to track the runtime evaluation of code by an ECMAScript implementation.

결국, 코드가 어떻게 런타임 때 평가되고 추적되는지를 판단하는 것이 실행 컨텍스트에요!

즉, 이전의 평가 - 실행 단계가 컴파일 때에 초점을 맞췄다면,
우리는 이제 런타임 때 평가 및 추적이 어떻게 되는지를 살펴보는 것!
꽤나 중요한 내용인지라, 한 번 살펴 보는 것이 좋겠군요 🙇🏻‍♂️

자바스크립트의 소스코드 타입

일단 자바스크립트에서는 다음 4가지 소스코드 타입이 있어요.
이러한 소스코드 타입들은 실행 컨텍스트 생성 과정이 조금씩 상이합니다!

소스코드의 타입설명실행 컨텍스트 생성 과정
전역 코드전역에 존재하는 소스코드. 이때, 전역에 정의된 함수, 클래스 등의 내부 코드는 포함 X전역 스코프 생성 → 전역 함수를 전역 객체의 프로퍼티, 메서드로 바인딩 → 전역 객체와 연결 → 전역 코드 평가 후 생성
함수 코드함수 낸부에 존재하는 소스코드. 이때 중첩 함수, 중첩된 클래스 등의 내부 코드는 포함 X지역 스코프 생성 → 매개변수, 지역 변수, arguments 관리 → 지역 스코프를 스코프 체인에 연결 → 함수 코드 평가 후 생성
eval 코드eval은 런타임 이후에 동적으로 자신의 환경을 만든다 했죠? 그때의 소스코드입니다!자신만의 독자적인 스코프를 엄격모드에서 생성 → 코드 평가 후 생성
모듈 코드모듈 내부에 존재하는 소스코드. 모듈 내부 함수, 클래스 등의 내부 코드는 포함 X모듈별로 독립적인 모듈 스코프 생성 → 모듈 코드 평가 후 생성

소스코드의 평가와 실행

자바스크립트는 소스코드를 2개의 과정으로 처리한다.

먼저 가장 중요한 개념이에요.

자바스크립트는 소스코드의 평가 / 소스코드의 실행으로 나누어 코드를 처리해요 🙇🏻‍♂️

소스코드의 평가

소스코드의 평가 과정에서는 선언문들을 모두 관리해줘야겠죠?
따라서 변수 식별자와 함수 식별자를 스코프에 등록하는 과정을 거칩니다!
이렇게 등록을 모두 완료하게 되면, 비로소 실행 컨텍스트가 생성이 되는 거에요 😉

소스코드의 실행

이제 우리는 선언문을 제외한 코드들을 한 줄씩 순차적으로 실행하게 될 거에요.
그러면, 이제 우리는 스코프 체인을 통해 변수를 검색하게 된답니다.
따라서 이를 통해 원하는 값을 도출하면, 이를 실행 컨텍스트에 등록시키는 거에요!

💡 응? 뭔가 이전에 배웠던 평가 단계 내용 반복 아닌가요?
반복되는 내용도 있지만, 우리는 이제 좀 더 실행 단계에 초점을 맞추어 이 파트를 이해할 거에요! 이 점 염두해주세요 :)

실행 컨텍스트는 실행 과정에 깊은 관련이 있다.

일단 우리, 코드를 평가한 후에 비로소 이를 기반으로 실행 컨텍스트가 생성이 된다고 했죠?
따라서 이 파트는, 이제 어떻게 실행할 건데?에 대한 질문의 답변이 될 수 있겠군요! 🙆🏻

실행 컨텍스트는 소스코드 실행에 대한 환경을 제공하고, 결과를 관리하는 영역입니다.
이때, 크게 2가지로 나누어서 처리해요.

  • 등록된 식별자와 스코프: 렉시컬 환경
  • 실행 순서: 실행 컨텍스트 스택

이제 우리는 이 두 가지를 집중할 필요가 있겠군요!

실행 컨텍스트 스택

실행 컨텍스트는 스택 자료구조를 채택했습니다!
이 역시, 결국 상위 컨텍스트가 없어진다는 것은 하위 컨텍스트가 모두 제거되어야 가능하다는 것을 전제로 하기 때문이겠죠!

쌓이는 기준은, 한 줄씩 코드를 실행할 때마다, 코드의 제어권을 누가 갖게 되는지에 따라 결정이 되는 거에요.

예컨대, 함수가 현재 호출이 되었다!하면, 결과적으로 제어권은 함수가 갖고 있는 것이니 최상위의 스택에는 해당 함수 컨텍스트가 존재하게 되는 것이죠!

반대로, 이 함수가 모두 실행되어 종료된다면 더이상 실행 컨텍스트에 남아있지 않게 되는 것이죠. 😆

결국, 이 책의 저자 블로그에서 발췌한 실행 컨텍스트를 그림으로 표현하자면, 다음처럼 전개가 된다는 거에요!

렉시컬 환경

23-1. 환경 레코드 글을 참조해주세요!
설명이 너무 길어져서, 이는 분리해서 글을 포스트 했습니다.


실행 컨텍스트의 컴포넌트 구성

이제 본격적으로 실행 컨텍스트를 알아보려 하는데요!
실행 컨텍스트의 컴포넌트는 크게 다음 4가지로 구성이 되어 있어요.

  • 코드 평가 상태 컴포넌트: 실행 컨텍스트와 연관된 코드의 실행 / 중단 / 재평가를 하는데 사용돼요!
  • 함수 컴포넌트: 실행 코드가 함수 객체라면 값이 함수 객체이고, ScriptModule 코드를 평가하고 있다면 해당 컴포넌트의 값은 null이에요!
  • Realm 컴포넌트: ECMAScript의 자원들에 접근하는 것과 연관된 Realm Record
  • ScriptOrModule 컴포넌트: 코드의 발생과 연관된 모듈, 스크립트에 관한 컴포넌트입니다! 만약 없다면 값이 null이에요.

이때 추가적으로 2개의 상태 요소를 가지고 있어요.
이 두개의 컴포넌트는 항상 환경 레코드입니다!

  • LexicalEnvironment: 렉시컬 스코프 선언과 관련한 환경
  • VariableEnvironment: 함수 레벨 스코프 선언과 관련한 환경 (hoisting)

위 둘은 일반적으로 초기화 당시에는 같은 렉시컬 환경을 가지지만, 이후 맥락에 따라서 분리되기도 하고, 내용이 변경되기도 해요.
따라서 원래는 엄밀히 말하면 렉시컬 환경과 변수 환경이지만, 이해를 돕기 위해 이를 렉시컬 환경이라고 칭하는 겁니다!

실행 컨텍스트의 생성, 식별자 검색 과정

전역 객체 생성

맨 처음에는 전역 객체가 전역 코드를 평가하기 이전에 실행됩니다. 일단 환경을 조성해야 하기 때문이죠!

따라서 빌트인 전역 프로퍼티, 빌트인 전역 함수, 표준 빌트인 객체, 호스트 객체 등을 포함해줍니다 😉

쉽게 말하자면, 전역에서 쓸 수 있는 것들 모두 한데 전역 객체에 모아주었다는 거죠!

전역 코드 평가

이제 환경이 조성 되었으니, 평가할 준비가 된 거에요!
평가는 다음과 같은 절차를 거치게 된답니다.

  1. 전역 실행 컨텍스트 생성
  2. 전역 렉시컬 환경 생성
    2-1. 전역 환경 레코드 생성 (객체 환경 레코드 생성, 선언적 환경 레코드 생성)
    2-2. this바인딩
    2-3. 외부 렉시컬 환경에 대한 참조 결정

전역 실행 컨텍스트 생성

일단 스택에 추가!

전역 환경 레코드 생성

앞서 설명 드렸듯, 전역 환경 레코드는 논리적으로는 단일 레코드이지만, 객체 환경 레코드와 선언적 환경 레코드를 감싸고 있어요!

  • 객체 환경 레코드 생성: BindingObject(전역 객체)와 바인딩이 되고, var과 함수 선언문으로 정의된 전역 함수를 해당 객체의 프로퍼티에 포함시켜요!

  • 선언적 환경 레코드 생성: 그러면 이제 함수 선언문, var이 아닌 것들은 이곳에서 등록하고 관리해주겠죠!

    이 렉시컬 환경의 일부인 선언적 환경 레코드가 관리해주는 것 중 하나가 TDZ입니다!


this 바인딩

이후, 전역 환경 레코드의 [[GlobalThisValue]]this를 바인딩해줘요!
이로 인해, this를 전역 코드에서 참조하면, [[GlobalThisValue]]에 바인딩 된 객체가 전역 객체이므로, 전역 객체를 반환하는 거에요. 🙇🏻‍♂️

💡 참고로 this 바인딩은 오직 전역 환경 레코드와 함수 환경 레코드에만 존재합니다! 잘 생각해보니, 정말 this를 쓸 경우가 이 두 가지밖에 없네요!

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

상위 스코프를 결정하는 과정인데요!
전역 코드를 포함하는 상위 개념의 소스 코드는 존재하지 않으므로, null이겠죠?
이를 통해, 스코프 체인의 종점이 전역 객체임을 알려주는 거에요!

전역 코드 실행

자, 이제 코드들을 평가를 완료했으니, 한 줄씩 실행하며 검색하면서 값을 관리해주면 돼요!

여기서는 단순합니다. 식별자 검색을 해주는 건데요!
우리가 쓴 식별자가 어떤 것이 맞는 식별자인지를 결정해주면 된답니다 😉
이는 우리가 알던대로, 스코프 체인을 이용해서 관리해주는 거에요!

함수 코드 평가

만약 코드 안에 함수가 있다면, 함수 코드를 평가하겠죠?
그러면 다음과 같은 과정을 또 생성하겠군요!

  1. 함수 실행 컨텍스트 생성
  2. 함수 렉시컬 환경 생성
    2-1. 함수 환경 레코드 생성
    2-2. this 바인딩
    2-3. 외부 렉시컬 환경에 대한 참조 결정

음... 2-1도 아까 위에서 설명했으니, 2-2와 2-3만 추가적으로 살펴볼게요.

함수 코드 평가에서 this를 바인딩 할 때에는 [[GlobalThisValue]] 내부 슬롯이 아닌, [[ThisValue]] 내부 슬롯에 this가 바인딩이 된다는 것만 유념하면 되겠군요!

뭔가 설명이 너무 대충인 것 같지만, 정말 이게 다에요! 오히려 중복된 내용을 더 구구절절하게 설명하면 헷갈릴 것 같아서, 핵심만 이야기해보려 합니다. 🙆🏻

2-3에서는 이제 상위 렉시컬 환경을 결정해야 하는데요!
자바스크립트에서는 함수가 렉시컬 스코프를 따른다고 했죠?
따라서, 함수를 선언한 시점에 어떤 실행 컨텍스트를 실행하고 있는지를 따져보고, 선언된 시점의 실행 컨텍스트의 렉시컬 환경을 참조하게 되는 거죠!

내부 중첩 함수도 위와 같은 로직으로 쭉~ 전개됩니다.

함수 코드 실행 종료

만약 함수 코드가 실행됐었고, 모든 코드를 다 실행하여 종료할 시점에는 이제 컨텍스트 스택 상에서 빼냅니다.

전역 코드 실행 종료

이제 모든 것들이 다 종료되고 전역 코드마저 실행이 완료되면 끝이죠 👏🏻

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

사실 이 파트 굉장히 중요한 부분 맞긴 한데, 우리가 이해했던 걸 쭉~ 잘 따라왔다면 문제가 없어요.

우리 실행컨텍스트의 구성에서 추가적으로 상태 컴포넌트 관련한 2개가 있다고 했죠?

  • LexicalEnvironment
  • VariableEnvironment

이 두 개는 처음에는 초기화되지만, 나중에 새롭게 렉시컬 환경이 추가되기도 한다는 이야기를 위에서 했었어요.

💡 지금이 바로 그런 경우입니다!

반복문이라던지, 조건문과 같은 블록문은 나올 때마다, 새롭게 자신만의 렉시컬 환경을 갖게 되는 거에요!

그렇다면 외부 렉시컬 환경은 무엇이 될까요?
바로 상위 렉시컬 스코프인, 조건문을 감싸고 있는 블록문이 되겠군요! 간단하죠? 😆

🎉 마치며

사실... 원문을 제대로 이해하지 못해서 정말 반나절을 이 파트에 쏟았던 것 같아요.
그렇지만, 나중에 제가 시니어나 리드 개발자가 되었을 때, 이러한 하나하나의 개념에 이제는 휘둘리지 않고, 헷갈리지 않을 거 같아요!

특히 환경 레코드를 쭉 원문을 기반으로 훑어본 경험은, 앞으로도 자바스크립트를 이해하는 데 제 무기가 될 것 같기도 합니다. 이상! 🌈


📁 참고자료

https://262.ecma-international.org/5.1/#sec-10.3
https://tc39.es/ecma262/#sec-declarative-environment-records
https://github.com/tc39/ecma262/issues/736

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

0개의 댓글