[JavaScript] Execution context(실행컨텍스트)

Mandy·2023년 1월 21일
2
post-thumbnail
  • 실행컨텍스트(Execution context)

    배경지식 Check!

    ⚠ 실행 컨텍스트를 이해하기 위해서 스택이란 LIFO(Last In First Out) 구조라는것을 알아야 함.

    ⚠ 실행 컨텍스트를 이해하기 위해서 호이스팅 개념을 알아야 함.
    ⚠ 간단한 js 코드를 읽고 이해할 수 있어야 함.


    실행 컨텍스트(Execution context)는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체 이다.

이미지 출처 : https://youtu.be/EWfujNzSUmw

우리가 실행 컨텍스트에 대해 알아보기 위해서 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아둔 “객체” 라는 것과 VariableEnvironment, LexicalEnvironment,environmentRecord, outerEnvironmentReference 를 포함한다는 사실을 알아야한다.

환경 정보란 쉽게 생각하자면, 전역 공간이나 함수 내부에서 사용하는 모든 변수나 함수들의 정보이고, 실행 컨텍스트가 코드를 실행함에 있어서 필요한 도구들을 모아놓은 상태라고 받아들이면 이해가 쉽다.

위의 설명만 보고 음 그렇군! 하고 이해할 사람은 극소수일 것이다.

우선 우리가 실제로 자바스크립트로 개발할 때 자주 겪었던 현상을 먼저 돌이켜보면 이해를 도울수 있을것 같다.

예시)

var fruit = 'apple';
const milk = 'seoul';

function blender() {
	var vegetable = 'salary';
	const fruit = 'banana';
	console.log(vegetable, fruit, milk);
}

blender();

위와 같은 js코드가 있다고 가정하자.

매우 간단한 자바스크립트 코드로 보인다.

  1. 전역공간에서 변수 fruit 와 milk를 선언하고, blender라는 함수를 선언했다.
  2. 이 때, 함수 내부에서 또다시 vegetable 와 fruit 변수를 선언하고 콘솔로그로 vegetable, fruit, milk를 출력하는 동작을 하도록 한다.
  3. blender 함수를 호출함으로써 위 동작을 수행하게 된다.

이러한 동작의 흐름은 js로 개발해본 사람이라면 이해가 어렵지 않고, 익숙할 것이다!(그치만 var가 매우 거슬려요)

그런데, 우리는 이 코드에서 blender 함수가 실행될 때 콘솔로그에 무엇이 찍힌다고 예상하게 될까?

“salary banana seoul” ?

혹은

“salary apple seoul” ?

그것 아니면

“salary banana undefined” ?


잘 모르겠다면 개발자 도구(F12)를 켜서 위 코드를 입력해보면 된다.

보다시피 “salary banana seoul”이 나오게 된다.

자바스크립트에 익숙한 당신이라면, 이 답을 알고 있었을지도 모른다.

실행 컨텍스트를 정확히 알지 못하더라도 개발을 하는 과정에서 “내부 함수에서 외부 함수의 변수를 부르는 일이 가능하다.” 라는 것을 깨달을 수 있기 때문이다.

이 코드에서는 내부 함수는 blender이고 외부 함수는 전역 공간일 것이다.

그런데, 어떻게 이런 현상이 발생하는 것일까? 그리고 왜 이런 현상이 일어나는 것일까? 이것에 대해 본격적으로 파헤쳐보자.

이 코드를 실행하게 되면

위 그림과 같이 콜스택(CallStack)에 컨텍스트가 담긴다.

컨텍스트가 정확히 무엇인지 지금은 아마 이해하기 어려울수 있다. 그러니, 단순하게 어떤 코드들의 모임 이라고 생각해보자.

전역 컨텍스트란, 전역에 존재하는 모든 코드들의 모임, blender 실행 컨텍스트란, blender 함수에 존재하는 모든 코드들의 모임 이렇게 말이다.

또한, 콜스택(CallStack)에 대해 처음 들어볼 수 있는데, 이것은 자바스크립트 엔진인 v8의 메모리구조에 포함되는 공간이라고 보면된다.

코드가 실행되는 동안 실행될 코드들이 스택으로 쌓이고 스택의 후입선출(LIFO:Last In First Out) 방식으로 코드를 실행하도록 하는 공간이라서 이름도 콜스택이다.

자세한 것은 v8에 대해 알아보면 더 정확히 알 수 있지만 여기서는 실행 컨텍스트의 설명을 위해 콜스택에 대한 것은 이정도로 설명을 마치겠다.

※ 코드가 실행되는 경우 주황색, 실행이 일시적으로 멈추는 경우 회색으로 표현하였다.

  1. 처음에는 전역 컨텍스트와 관련된 코드를 진행하기 위해 전역 컨텍스트를 콜스택에 담고 실행한다.
  2. blender 함수를 실행하는 순간 blender 함수의 환경 정보들을 수집하고 blender 실행 컨텍스트를 생성한 후 콜스택에 담는다. 이때, 기존에 전역 컨텍스트에 포함된 코드들을 실행하던 것을 일시 정지 후 blender 실행 컨텍스트 코드를 실행시킨다.
  3. blender 실행 컨텍스트가 모두 수행된 이후 콜스택에서 제거되면 콜스택 최상단에 남은 것은 전역 컨텍스트 이기 때문에 전역 컨텍스트가 이전에 실행 중지된 시점부터 다시 코드가 실행된다.
  4. 전역 공간에 실행할 코드가 없다면 전역 컨텍스트도 코드 실행 완료 후 콜스택에서 제거되며, 콜스택에 아무것도 남지 않은 상태로 종료된다.

컨텍스트들의 실행 순서는 알게되었다.

그런데, 앞서 언급했던 VariableEnvironment, LexicalEnvironment,environmentRecord, outerEnvironmentReference 들과 이것이 무슨 상관일까?

영어로 읽기에 너무 긴 문장이므로, 편의상 위 네가지 요소를

VE(VariableEnvironment), LE(LexicalEnvironment), 레코드(environmentRecord), OER(outerEnvironmentReference)라고 부르고 하나하나 무슨 역할을 하는지 알아보겠다.

자바스크립트 엔진이 실행 컨텍스트를 구성하고 코드를 실행할때, 가장 먼저 하게되는것은 바로 레코드(environmentRecord)에 전역 컨텍스트 및 blender실행 컨텍스트 식별자 및 식별자에 바인딩 된 값이 기록하는 것이다.

그렇기에 자바스크립트 엔진은 blender 함수 호출로 인해 실행컨텍스트를 생성 후 blender 함수내에 선언된 변수명 (vegetable가 salary이고, fruit가 banana인 것) 들을 이미 알고 있다!

environmentRecord: 네가 쓴 변수를 이미 알고있다.

이러한 이유로 우리는 마치 식별자들을 최상단에 끌어올려서 정보를 아는 것 같은 현상인 “호이스팅”을 보게되는 것이다.

즉, 레코드(environmentRecord)는 실행 컨텍스트 내부 전체를 처음부터 끝까지 확인하며 순서대로 값을 수집하여 기록한다고 볼 수 있다.

VE(VariableEnvironment)는 무엇을 말하는 것일까?

바로, 현재 컨텍스트 내부의 식별자, 레코드, 외부 환경 정보, 외부 환경 참조가 모두 포함된 것이다. 방금 레코드(environmentRecord)가 실행 컨텍스트 전체를 스캔하면서 값을 수집한다고 했는데, 그 수집된 정보들을 포함한 값이 들어있다.

LE(LexicalEnvironment)는 VE(VariableEnvironment)의 초기 상태를 기억하고 있지만 코드가 실행되면서 변경되는 사항들이 실시간으로 적용되는 것이다. 가장 최신의 상태, 정보들을 저장하고 있다.

마지막으로 OER(outerEnvironmentReference) 에 대해 알아보자.

OER(outerEnvironmentReference)은 현재 호출된 함수가 선언될 당시LE(LexicalEnvironment)를 참조한다.

선언될 당시” 에 굉장한 강조 표시를 했는데, 외부 환경 참조는 오직 자신이 선언될 당시의 LE(LexicalEnvironment)만을 참조하기 때문에 순차적으로만 접근이 가능하며, 여러 컨텍스트에 동일 식별자를 생성하더라도 가장 먼저 발견된 식별자에만 접근할 수 있다.

위의 코드로 보자면 blender 실행 컨텍스트 내에서 콘솔 로그에 fruit 를 출력하고 있는데, 이 때 출력되는 fruit 는 blender 함수내에서 선언된 fruit 이며 전역에서 선언된 fruit 는 출력되지 않는다. 따라서 콘솔 로그에는 salary banana가 출력될 것이다.

다시 한번 말하자면, 여러 컨텍스트에 동일 식별자를 생성하더라도 가장 먼저 발견된 식별자에만 접근할 수 있다.

바꿔말하자면, 만일 해당 컨텍스트내에 식별자가 없을 경우 그 상위 컨텍스트에서 찾게된다. 그리고 최종적으로는 전역 실행 컨텍스트까지 가서 식별자를 찾게되는데, 이때도 발견하지 못할경우 undefined와 Error를 출력시키는것이다.

이해를 돕기 위해 위의 예시 코드를 아래와 같이 변형해보자.

var milk = 'seoul';

function blender() {
	var vegetable = 'salary';
	const fruit = 'banana';
	console.log(fruit , milk);
}

function cup() {
	blender();
};

cup();


위 코드를 그림으로 표현한 것이다.

  1. 우선, cup 함수를 실행하면 cup 함수는 blender함수를 호출하므로 blender 함수가 실행된다.
  2. blender함수 내에서 fruit과 milk를 콘솔로그로 출력해야 하는데, 현재 blender 컨텍스트 내부에 존재하는 식별자는 fruit뿐이다.
  3. 이 때, OER(outerEnvironmentReference)이 자기의 상위 컨텍스트를 방문하며 knock knock한다.

“(똑똑똑) 혹시, 우리 milk 못보셨나요?”
4. 바로 위 컨텍스트인 cup에서는 milk가 선언되어있지 않으므로 cup의 상위이자 최상위 컨텍스트인 전역 컨텍스트까지 방문한다.
5. 전역 컨텍스트에서 milk를 발견하고 해당 값을 콘솔로그로 출력할 수 있게 된다!
(banana seoul이 출력된다.)

이와 같은 과정을 스코프체인(Scope Chain)이라 하며, OER은 스코프 체인을 가능하게 해준다.

결론적으로 OER은 해당 함수 선언이 된 시점에서 렉시컬 환경을 참조하여 변수에 접근하고 변수를 찾지 못할 경우 찾을때까지 탐색하는 과정을 반복한다.

VariableEnvironment, LexicalEnvironment, environmentRecord, outerEnvironmentReference 는 즉,  실행할 코드에 제공할 환경 정보들을 모아놓은 객체의 일부이며

그 객체가 바로, 실행 컨텍스트이다.

실행 컨텍스트의 개념 자체도 중요하지만, 그것을 이해하기 위한 과정들을 하나하나 이해하는것이 중요한 것 같다.

-끝

참고한 자료
https://m.youtube.com/watch?v=QtOF0uMBy7k (생활코딩)
https://wit.nts-corp.com/2013/09/10/120 (번역 글, 원문 출처 : http://dmitrysoshnikov.com/ecmascript/chapter-1-execution-contexts/)
https://gamguma.dev/post/2022/04/js_execution_context
https://youtu.be/EWfujNzSUmw (우아한 Tech - 10분 테코톡)
https://www.inflearn.com/questions/134138/variableenvironment-lexicalenvironment%EC%B0%A8%EC%9D%B4%EC%A0%90

더 심화된 내용을 보고싶다면...
https://meetup.nhncloud.com/posts/86 (NHN 개발 블로그)
https://meetup.nhncloud.com/posts/129 (NHN 개발 블로그)

profile
즐코 행코 하세용

0개의 댓글