gamguma

JS Execution Context (실행 컨텍스트) 란?

코어자바스크립트를 읽고 얻은 지식 중 실행 컨텍스트, 콜스택 | JS Execution Context, JS CallStack

⚠️

이 글은 800 전에 작성되었습니다.
최신 정보가 아닐 수 있습니다.

Execution Context (실행 컨텍스트) 란

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다.
자바스크립트는 동일한 환경에 있는 환경 정보들을 모은 실행 컨텍스트를 콜스택에 쌓아올린 후 실행하여 코드의 환경과 순서를 보장할 수 있게 됩니다.

풀어서 설명하면 스택의 경우 FILO (First In, Last Out) 의 구조이기에 순서를 보장, 콜스택 내부에 쌓인 실행 컨텍스트의 정보를 통해 환경을 보장할 수 있는 것이라 생각합니다.

여기서 환경이란 전역공간이 될 수 있고, 함수 즉 함수 내부의 환경이 될 수 있습니다.

var temp = 'temp';
 
function b (){
  console.log('안녕하세용');
}
 
function a (){
  b();
}
 
a();
context_example

처음 자바스크립트 코드를 실행하는 순간 사진의 (1) 처럼 전역 컨텍스트가 콜스택에 담깁니다.
브라우저의 경우 window, node 환경의 경우 global 같은 객체를 사용할 수 있는 이유입니다. 요건 하단의 outerEnvironmentReference 를 읽으시면 이해하실 수 있습니다.

  • (1) 콜스택엔 전역 컨텍스트를 제외하곤 다른 컨텍스트가 없기에 전역 컨텍스트와 관련된 코드를 진행합니다.
  • (2) 전역 컨텍스트와 관련된 코드를 진행 중 a함수를 실행하였기에 a 함수의 환경 정보들을 수집하여 a 실행 컨텍스트를 생성, 콜스택에 담습니다.
    콜스택 최상단에 a 실행 컨텍스트가 있기에 기존의 전역 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한 후 a 실행 컨텍스트의 코드를 실행합니다.
  • (3) a 함수 내부에서 b 함수를 실행하였기에 b 함수의 환경 정보들을 수집, 실행 컨텍스트를 생성, 콜스택에 담습니다. 이전과 똑같이 콜스택 최상단에 b 실행 컨텍스트가 있기에 기존 a 실행 컨텍스트와 관련된 코드의 실행을 일시적 중단합니다.
  • (4) b 함수가 종료된 후 b 실행 컨텍스트가 콜스택에서 제거됩니다. 제거 후 콜스택 최상단에는 a 실행 컨텍스트가 있기에 이전에 중단된 지점부터 코드 진행이 재개됩니다.
  • (5) a 함수 또한 종료된 후 실행 컨텍스트가 콜스택에서 제거됩니다.
  • 이후엔 전역 공간에 실행할 코드가 남아있지 않다면 콜스택에서 전역 컨텍스트 또한 제거되며 콜스택에 아무 것도 남지 않은 상태로 종료됩니다.

Execution Context (실행 컨텍스트) 속엔 어떤 정보가 있을까

우선 실행 컨텍스트 내부엔 variable environment, lexical environment, this binding 가 있습니다.

VariableEnvironment

VariableEnvironment 란 현재 컨텍스트 내부의 식별자 정보 environmentRecord, 외부 환경 정보 outerEnvironmentReference가 포함되어 있습니다.
VariableEnvironment 에 먼저 정보를 담고, 그대로 LexicalEnvironment 에 복사해 사용한다고 합니다.

LexicalEnvironment

LexicalEnvironment 는 초기에는 VariableEnvironment 와 같지만 변경 사항이 실시간으로 적용됩니다.
즉, VariableEnvironment 초기 상태를 기억하고 있으며, LexicalEnvironment 최신 상태를 저장하고 있습니다.

environmentRecord

environmentRecord 란 현재 컨텍스트와 관련된 식별자와 식별자에 바인딩된 값이 기록되는 공간입니다.
더불어 실행 컨텍스트 내부 전체를 처음부터 끝까지 확인하며 순서대로 수집합니다.

전혀 이상할 것 없는 코드 예시를 보여드리겠습니다.

var gamguma = 'junhwan';
 
console.log(gamguma); // junhwan

다만 이번엔 조금 다릅니다!

console.log(gamguma); // undefined
 
var gamguma = 'junhwan'; 

선언하기도 전에 값을 호출했는데 Reference error 가 발생하지 않고 undefined (할당되지 않음) 만 출력되네요?!
이것은 자바스크립트의 호이스팅이라는 현상과 관련이 있습니다.

호이스팅이란?
자바스크립트 엔진이 실행 컨텍스트를 구성할 때 environmentRecord 에 식별자의 정보를 수집합니다. 이러한 과정을 통해 엔진은 함수를 실행하기도 전에 해당 컨텍스트 내부의 변수명들을 이미 알고있습니다.
이렇기에 식별자들을 코드의 최상단으로 끌어올렸다! 라는 호이스팅이라는 개념이 생겨납니다. 물리적으로 끌어올린 것이 아닌, 실행 컨텍스트 관점에선 이미 식별자들의 정보를 알고 있으니 식별자 정보를 수집하는 과정을 이해하기 쉬운 방법으로 나타낸 추상화한 가상 개념입니다.
var 와 let, const 의 경우 호이스팅 + 선언 개념이 조금 다르기에 추후 포스트를 작성할 예정입니다! ex) TDZ,, initialization,,

결론
LexicalEnvironment 의 environmentRecord 의 경우 해당 컨텍스트 환경에 필요한 식별자와 식별자의 값이 기록되며, 함수 실행 시 실행 컨텍스트가 생성되므로 (함수 실행보다 environmentRecord 수집이 먼저 되므로) 변수와 같은 식별자를 끌어올리는 것과 같다 라는 개념의 호이스팅이 생겨났습니다.

outerEnvironmentReference

const hello = 'hello';
 
const gamguma = () => {
  const gamja = {
    age: 10,
    breed: 'yorkshire terrier'
  };
  const guma = {
    age: 4,
    breed: 'yorkshire terrier'
  };
  console.log(hello);
  console.log(gamja);
  console.log(guma);
}
gamguma(); //'hello', {age: 10, breed: 'yorkshire terrier'}, {age: 4, breed: 'yorkshire terrier'}
console.log(gamja); //ReferenceError: gamja is not defined
console.log(guma); //ReferenceError: guma is not defined

간단한 코드 예시를 보여드렸습니다.
우선 해당 코드의 함수 내부에선 외부의 hello 가 접근 가능하며, 당연히 내부에서 선언한 gamja, guma 또한 접근할 수 있었습니다.

다만 외부에선 내부에 선언된 gamja, guma 를 접근할 수 없었는데요? 이건 outerEnvironmentReference 덕분에 가능합니다.

outerEnvironmentReference 란 현재 호출된 함수가 선언될 당시LexicalEnvironment 를 참조합니다.
여기서 선언될 당시가 중요한데요, gamguma 함수가 선언될 당시의 outerEnvironmentReference 는 글로벌 실행 컨텍스트의 LexicalEnvironment 를 참조하고 있으며, 해당 환경의 environmentRecord 에 hello 와 같은 변수의 정보들이 기록되어 있습니다.

그렇기에 함수 내부에선 outerEnvironmentReference 를 통해 상위 컨텍스트의 LexicalEnvironment 에 접근하여 environmentRecord 에서 변수인 hello 를 사용할 수 있게 되는 것입니다.

더불어 outerEnvironmentReference 는 오직 자신이 선언될 당시의 LexicalEnvironment 를 참조하기에 순차적으로만 접근이 가능하며, 여러 스코프에서 동일한 식별자를 생성하였다 하더라도 가장 먼저 발견된 식별자만 접근이 가능합니다.

outerEnvironmentReference
const message = 'hello';
const hihi = 'hihi';
 
const sayHi = () => {
  const message = 'hi';
  console.log(message);
  console.log(hihi);
  console.log(hello)
}

message 변수
현재 컨텍스트의 LexicalEnvironment => environmentRecord 에 message 라는 식별자가 있고,
outerEnvironmentReference => LexicalEnvironment => environmentRecord 에도 message 식별자가 있지만
가장 먼저 발견된 식별자에 바인딩 된 값인 'hi' 출력, 알게모르게 전역 컨텍스트에서 선언한 message 의 경우 변수 은닉화가 됨

hihi 변수
현재 컨텍스트의 LexicalEnvironment => environmentRecord 에 hihi 라는 식별자가 있으므로, outerEnvironmentReference 참조하여 전역 컨텍스트의 LexicalEnvironment 참조, environmentRecord 의 hihi 식별자에 접근하여 'hihi' 출력

hello 변수
현재 컨텍스트의 LexicalEnvironment => environmentRecord 와 outerEnvironmentReference => LexicalEnvironment => environmentRecord 없으므로 즉, 콜스택의 최하단에 위치한 전역 실행 컨텍스트에도 없다면 undefined 를 출력



결론
outerEnvironmentReference 란 해당 함수가 선언된 위치의 LexicalEnvironment 를 참조하며, 변수에 접근을 한다면 해당 LexicalEnvironment 에서 발견된다면 사용, 찾지 못할 경우 다시 outerEnvironmentReference 참조하여 탐색하는 과정을 반복합니다. 이러한 과정을 스코프 체인 이라고 하며 outerEnvironmentReference 는 스코프체인을 가능케하는 역할입니다.

ThisBinding

저는 자바스크립트에서 this 키워드가 가장 헷갈렸습니다.
프로그래밍 자체를 자바스크립트로 접했는데 this 키워드 자체를 많이 접해보지 못해서 그런 것 같습니다,,
우선 this 는 컨텍스트를 가르킨다고 생각합니다. method 에서 사용시 해당 method 가 담겨있는 instance or object 를 가르키며, 함수표현식에서 사용시 this 를 바인딩하지 않는 이상 전역 객체를 가르킵니다.

더불어 전역 공간에서 this 는 함수표현식과 같이 전역 객체를 가르킵니다.

ES6 의 화살표함수를 사용하면 this 바인딩, 우회법으로 변수에 this 를 담아 실행할 필요 없이 함수를 실행한 컨텍스트를 바라봅니다.

const obj = {
  outer: function () {
    console.log(this); // 메소드에서 사용되었기에 obj 출력
    function inner() {
      console.log(this); // 함수표현식에서 사용되었기에 전역객체 출력
    }
    inner();
 
    const self = this;
    function inner2() {
      console.log(self);  // 우회하기위해 this 를 self 에 할당, 출력하였기에 obj 출력
    }
    inner2();
    const inner3 = () => {
      console.log(this);  // 화살표함수 사용으로 this 는 상위 스코프의 컨텍스트를 가르킴, obj 출력
    };
    inner3();
  },
};
 
obj.outer();

요약
this 란 실행 컨텍스트 생성시 즉, 함수를 실행할 때 할당해주는 것으로 상황에 따라 가끔은 전역객체, 가끔은 instance 를 가르킵니다.
여튼 실행 컨텍스트에서의 this 로 지정된 객체의 정보를 갖고 있는 것이 ThisBinding 입니다.

후기

이렇게 실행 컨텍스트는 VariableEnvironment, LexicalEnvironment, environmentRecord, outerEnvironmentReference, ThisBinding 등등 여러 정보들이 합쳐져 실행 컨텍스트가 되고, 이것이 콜스택에 쌓여 우리가 작성한 코드가 실행됩니다.

지금까지 자바스크립트를 사용하며 내부 동작이 어떻게 되는지도 자세히 모르고 사용해왔습니다.

콜스택이 무엇인지, 실행 컨텍스트가 무엇인지, 자바스크립트 엔진이 어떻게 동작하는지 자바스크립트와 리액트를 학습하며 자연스레 궁금했었는데 시간내어 공부하니 알아가는 재미도 있고 코드가 어떻게 실행되는지 배웠기에 코드 작성에도 큰 도움이 될 것 같습니다.

앞으로는 varlet, const 의 선언 차이, LexicalEnvironment 의 environmentRecord 에서 함수 표현식과 함수 선언식의 environmentRecord 수집 차이, 클로저, 비동기에 관해 포스팅해보도록 하겠습니다.

더불어 포스팅 작성하면서 어떻게하면 읽는 사람이 편하고 이해가 쉬울까 라는 고민을 하게 되는 것 같습니다. 좋은 징조겠죠?