실행 컨텍스트

skyu93·2024년 10월 28일

요약

자바스크립트는 코드를 '평가' 와 '실행' 두가지 과정을 거쳐 동작합니다.
평가 과정에서 실행 컨텍스트(렉시컬 환경)이 생성됩니다.
실행 과정에서 실행 컨텍스트의 내용을 확인하며 코드를 실행 합니다.

실행 컨텍스트 (Execution Context)

컨텍스트라는 단어는 “문맥”을 의미합니다. 이 개념을 활용해 자바스크립트 엔진의 실행 컨텍스트 동작 방식을 이해해보겠습니다.
자바스크립트 엔진은 사람의 사고 방식과 유사하다고 생각 합니다. 마치 사람이 문장을 읽고 이해하는 과정과 비슷하게 동작한다고 볼 수 있습니다.
예를 들어, 다음과 같은 문장을 읽는 상황을 생각해 봅시다.

“그가 물건을 부수고 도망쳤어.”

사람은 이 문장을 읽으면서 ‘그’, ‘물건’, ‘부수기’, ‘도망’이라는 단어를 바탕으로 문장의 전체적인 맥락을 이해하게 됩니다. ‘누가 물건을 부쉈나? 그가 했군. 그리고 도망갔네.’ 이렇게 순차적으로 문맥을 파악해 나갑니다. 하지만 이 문장만으로는 “그”가 누구인지 명확히 알 수 없으므로, 추가적으로 문장 앞뒤의 내용을 통해서 “그”라는 정보에 대해서 알아야합니다.

이처럼 사람의 사고 흐름과 유사하게, 자바스크립트 엔진도 코드를 해석하고 실행할 때 실행 컨텍스트를 설정하여 코드의 의미를 이해해 나갑니다. 코드에서 변수와 함수가 무엇을 지칭하는지 파악하고, 실행 순서에 따라 필요한 정보들을 구성하며 전체적인 실행 흐름을 조합합니다.

엔진은 코드를 어떻게 이해하고 실행 할까요?
자바스크립트 엔진은 소스코드가 로드되면 소스코드를 실행에 앞서 '평가' 과정을 거치며 코드를 실행하기 위한 준비를 합니다.
다시 말해 엔진은 소스코드를 이해하기위해 코드를 ‘평가'하고 '실행’ 과정을 나누어 처리합니다.

자바스크립트 엔진의 '평가'

평가 과정에서는 실행 컨텍스트를 생성합니다. 자바스크립트가 문맥을 만들어 이해하고 그 기반으로 실행하기 위함 입니다

이해를 돕기위해 아래 코드로 작성했습니다.
실제 자바스크립트 엔진의 코드가 아니오니 참고만 하시면 되겠습니다.

// 실제 코드
const aNum = 2;
const bNum = 3;
function sum(a, b) {
 return a + b;
}  


// 자바스크립트 엔진이 평가 후
const executionContext = {
  variables: { 
    'aNum': {
      isInitialization: false,
      value: undefined, 
      defineKeyword: 'const',
      ...
    },
    'bNum': {
      isInitialization: false,
      value: undefined, 
      defineKeyword: 'const',
     ...
    },
  },
  functions: {
  	'sum': { 
      arguments: [...], 
      localVariables: { ... },
      ...
    }
  },
}
              

평가 과정에서는 자바스크립트 엔진이 실행하기전 작성된 내용을 파악하고 실행 컨텍스트를 생성 합니다.
즉, 실행 컨텍스트는 자바스크립트가 실행할 수 있게 코드를 정보(번수, 함수, 클래스, 스코프, this 등)를 담고 있는 자료형입니다.

자바스크립트 엔진의 '실행'

자바스크립트 엔진은 코드가 실행되기전 평가 과정을 거쳐 생성된 실행 컨텍스트를 실행 컨텍스트 스택(콜스택)이라는 스택 자료구조에 등록합니다.
이 과정을 통해 실행 순서가 결정 됩니다.

여기서는 콜스택의 역할이나 이벤트루프 관련해서는 다루지 않습니다.

const aNum = 2;
const bNum = 3;

function sum(a, b) {
  return a + b;
}

sum(aNum, bNum); // sum 함수 실행 시 실행 컨텍스트 생성 및 콜 스택에 등록!

sum 함수가 호출되기전까지 sum의 실행 컨텍스트는 존재하지 않습니다.

// sum 함수 실행전 컨텍스트 스택 현황
JavaScriptEngine.executionContextStack = [executionContext]

처음 '평가' 과정에서 더이상 평가될 코드가 없다고 판단하고 자바스크립트 엔진은 '실행' 과정이 시작하게 됩니다.
'실행' 과정 중 sum 함수 호출 부분을 확인될때 sum 함수의 내부 내용을 '평가'하기 시작합니다.
'평가'가 완료되어 실행 컨텍스트가 생성되고 콜스택에 등록됩니다.

// sum 함수 호출후 컨텍스트 스택 현황
JavaScriptEngine.executionContextStack = [executionContext, sumFuncExecutionContext]

이러한 과정을 통해 자바스립트의 엔진은 실행 컨텍스트를 관리하고 실행하는 원리가 됩니다.

렉시컬 환경

엔진은 코드를 평가하고 실행할 때, 실행 컨텍스트를 생성한다고 설명드렸습니다. 이 실행 컨텍스트는 각 코드 블록이 평가될 때마다 생성되며, 현재 코드가 어디에서 선언되었는지에 대한 정보를 담고 있는것이 렉시컬 환경(Lexical Environment) 입니다.

실행 컨텍스트의 핵심 요소 중 하나로, 코드가 작성된 위치(렉시컬 스코프)에 따라 변수를 참조할 수 있는 구조입니다. 이를 통해 변수와 함수의 유효 범위를 관리하게 됩니다.

렉시컬 환경에 대한 이해는 호이스팅, 스코프 체인, 클로저, 그리고 this 바인딩 등 중요한 개념들을 파악하는 데 도움을 줍니다.

아래 코드를 예시로 들어 렉시컬 환경이 어떻게 설정되는지 알아보겠습니다.

function foo() {
  const a = 1;
  console.log(b);
  b = 3;
  
  function bar(c) {
    const d = 2;
    console.log(a + b + c + d);
  }
  bar(20);
  var b;
}
foo();

이 코드를 평가할 때, 자바스크립트 엔진은 foo 함수에 대한 실행 컨텍스트를 생성하며, 초기 설정은 다음과 같습니다

//
const fooExecutionContext = {
  lexicalEnvironment: {
    environmentRecord: {
      a: uninitialized,   
      b: initialized,
      bar: function() { /* bar 함수 정의 */ }
    },
    outerLexicalEnvironmentReference: globalExecutionContext.lexicalEnvironment
  },
  variableEnvironment: {
    b: undefined,
  },
  thisBinding: globalThis
};

여기서 주목할 점은 environmentRecord와 variableEnvironment입니다. environmentRecord는 현재 컨텍스트에서 사용할 변수와 함수 선언을 저장하며, variableEnvironment는 선언된 변수의 상태를 관리합니다.
초기 설정 시, const나 let으로 선언된 변수는 uninitialized 상태가 되며, var나 함수 선언은 초기화가 되는데 이것이 바로 호이스팅의 동작 원리 입니다.

이제 코드 실행 순서에 따라 fooExecutionContext의 변화를 살펴보겠습니다.

1) const a = 1;

const fooExecutionContext = {
  lexicalEnvironment: {
    environmentRecord: {
      a: initialized, // a가 초기화됨
      b: initialized,
      bar: function() { /* bar 함수 정의 */ }
    },
    outerLexicalEnvironmentReference: globalExecutionContext.lexicalEnvironment
  },
  variableEnvironment: {
    a: 1, // 값이 설정됨
    b: undefined,
  },
  thisBinding: globalThis
};

2) console.log(b);

console.log(b)가 실행되면, variableEnvironment에서 b의 현재 값인 undefined를 참조하여 출력합니다.

const fooExecutionContext = {
  lexicalEnvironment: {
    environmentRecord: {
      a: initialized,   
      b: initialized,
      bar: function() { /* bar 함수 정의 */ }
    },
    outerLexicalEnvironmentReference: globalExecutionContext.lexicalEnvironment
  },
  variableEnvironment: {
    a: 1,
    b: undefined, // `undefined`가 출력됨
  },
  thisBinding: globalThis
};

3) b = 3;

b에 값이 할당되면, variableEnvironment에서 b의 값이 3으로 업데이트됩니다.

const fooExecutionContext = {
  lexicalEnvironment: {
    environmentRecord: {
      a: initialized,   
      b: initialized,
      bar: function() { /* bar 함수 정의 */ }
    },
    outerLexicalEnvironmentReference: globalExecutionContext.lexicalEnvironment
  },
  variableEnvironment: {
    a: 1,
    b: 3, // undefined -> 3
  },
  thisBinding: globalThis
};

4) function bar(c) { ... }

bar 함수가 호출될 때 새로운 실행 컨텍스트가 생성됩니다.
여기서 주목할 점은 outerLexicalEnvironmentReference가 fooExecutionContext.lexicalEnvironment를 가리킨다는 것입니다.
즉, bar 함수 내부에서 상위 스코프의 변수(a, b)에 접근할 수 있게 되는 것입니다.

const barExecutionContext = {
  lexicalEnvironment: {
    environmentRecord: {
      b: uninitialized,
      c: uninitialized,
    },
    outerLexicalEnvironmentReference: fooExecutionContext.lexicalEnvironment
  },
  variableEnvironment: { },
  thisBinding: globalThis
};

이렇게 스코프 체인이 연결됨으로써, bar 함수 내부에서 정의되지 않은 변수를 참조할 때 outerLexicalEnvironmentReference를 따라 상위 스코프까지 순회하게 됩니다. 이로 인해 bar 함수가 외부 스코프에 있는 a, b 변수에 접근할 수 있습니다. 클로저는 이러한 스코프 체인을 통해 함수가 생성된 환경을 기억하고 유지하는 현상으로, 함수가 선언된 렉시컬 환경을 참조할 수 있도록 하는 중요한 개념입니다.

이와 같은 렉시컬 환경의 구조와 동작 원리를 이해하면, JavaScript의 변수 참조 방식인 스코프 체인을 명확히 파악할 수 있으며, 호이스팅과 클로저의 동작 원리를 자연스럽게 이해할 수 있습니다.

클로저
함수가 생성된 환경(렉시컬 환경)을 기억하고, 함수가 호출될 때도 외부 환경에 있는 변수를 계속해서 참조할 수 있게 하는 메커니즘입니다.

마지막

주니어 개발자에게 어려운 개념으로 여겨지는 호이스팅, 스코프, 클로저 등은 사실 자바스크립트의 실행 컨텍스트와 동작 방식을 이해하면 자연스럽게 익힐 수 있는 개념들입니다.
개념을 단순히 외우기보다는 자바스크립트의 동작 방식을 우리의 문장 해석 과정에 비유하여 생각해 보면, 자연스럽게 이해할 수 있고 기억에도 오래 남지 않을까 싶습니다.

자바스크립트가 식별자와 식별자에 바인딩된 값을 관리하는 방식과 호이스팅이 발생하는 이유, 클로저 동작 방식, 그리고 태스크 큐와 함께 동작하는 이벤트 핸들러와 비동기 처리 동작 방식을 이해할 수 있습니다.

  • 모던 자바스크립트 23장 실행컨텍스트, 359p
profile
느린 아이

0개의 댓글