본문 바로가기
JS

실행 컨텍스트(Execution Context)

by Hyeon_E 2024. 6. 10.

실행 컨텍스트

실행 컨텍스트란 실행할 코드에 제공할 환경 정보들을 모아놓은 객체

 

실행 컨텍스트는 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 객체를 구성하고, 이를 콜스택에 쌓아올렸다가 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장

어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행함

 

콜 스택(Call Stack)

Javascript는 전역 실행 컨텍스트 및 함수 실행 컨텍스트를 추적하기 위해 호출스택을 사용
해당 호출스택은 실행 컨텍스트 스택, 런타임 스택이라고 부름

 

스택은 LIFO 원칙으로 엔진이 스크립트 실행처음 실행할때, 전역 컨택스트를 생성하고 그 스텍에 push함

마찬가지로 함수가 호출될 때마다 해당 함수에 대한 함수 실행 컨텍스트를 생성하고 이를 호출 스택의 맨 위로 push하고 실행을 시작

 

함수의 실행이 끝나면, 가장 마지막에 실행되었던 함수 실행 컨텍스트를 호출 스텍에서 가장 먼저 제거

이런 방법을 게속 반복하며, 가장 늦게 들어온 순으로 실행 컨텍스트들을 pop

 

▶ 실행 컨텍스트의 구성

저장되는 환경 정보

  • VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보(선언 시점의 LexicalEnvironment의 스냅샷으로, 변경 사항은 반영되지 않음)
    • 스냅샷: 사진을 찍듯이 특정 시간(시점)에 데이터 저장 장치(스토리지)의 파일 시스템을 포착해 별도의 파일이나 이미지로 저장, 보관하는 기술
  • LexicalEnvironment : 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영
  • ThisBinding : this 식별자가 바라봐야 할 대상 객체를 의미

 

VariableEnvironment 초기 상태를 기억하고 있으며, Lexica Environment 최신 상태를 저장

즉, 초기화 과정 중에는 사실상 완전히 동일하고 이후 코드 진행에 따라 서로 달라지게 됨

 

VariableEnvironment와 LexicalEnvironment의 내부는 environmentRecordouterEnvironmentReference로 구성

 

EnvironmentRecord

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장

컨텍스트 전체를 처음부터 끝까지 훑으며 순서대로 식별자들을 수집하는데 여기에서 수집되는 식별자들은 매개변수 식별자, 선언된 함수, var로 선언된 변수의 식별자 등의 해당

 

environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지만을 먼저 수집하기 때문에, 변수를 인식할 때 식별자만 끌어올리고 할당 과정은 원래 자리에 순서대로 남겨둠(호이스팅)

 

호이스팅은 코드 해석을 좀 더 수월하게 하기 위해 environment Record의 수집 과정을 추상화한 개념으로, 실행 컨텍스트 관점에선 이미 식별자들의 정보를 알고 있으니 코드 집단의 최상단으로 이들을 '끌어올린다'고 해석하는 것

변수 선언과 값 할당이 동시에 이뤄진 문장은 '선언부'만을 호이스팅하고, 할당 과정은 원래 자리에 남아있게 되는데, 여기서 함수 선언문과 함수 표현식의 차이가 발생(함수 선언문 전체가 올라가고 함수표현식은 선언부만 올라감)

 

function a (x) {
  console.log(x);
  var a = 1;
}

a(1);

 

위의 코드에서, 실행 컨텍스트의 environmentRecord에는 식별자 x, a, b와 함수 foo를 수집

 

outerEnvironmentReference

outerEnvironmentReference란 해당 함수가 선언된 위치의 LexicalEnvironment를 참조를 가리킴

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

 

변수에 접근을 한다면 해당 LexicalEnvironment에서 발견된다면 사용, 찾지 못할 경우 다시 outerEnvironmentReference 참조하여 탐색하는 과정을 반복함

즉,  가장 가까운 스코프부터 시작해 외부 스코프를 차례로 탐색하는 것(이러한 과정을 스코프 체인이라고 함)

 

결론적으로, outerEnvironmentReference는 스코프 체인을 가능케하는 역할

 

const hello = 'hello';

const dog = () => {
  const happy = {
    age: 10,
  };

  console.log(hello);
  console.log(happy);
}

dog(); // 'hello', {age: 10}
console.log(hello); // 'hello'
console.log(happy); // ReferenceError: happy is not defined

 

dog함수가 선언될 당시의 outerEnvironmentReference는 글로벌 실행 컨텍스트의 LexicalEnvironment를 참조하고 있으며, 해당 환경의 environmentRecord에 hello와 같은 변수의 정보들이 기록되어 있음
그렇기에 함수 내부에선 outerEnvironmentReference를 통해 상위 컨텍스트의 LexicalEnvironment에 접근하여, environmentRecord 에서 변수인 hello를 사용할 수 있게 되는 것

 

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 출력

 

 

thisBinding

thisBinding은 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();

 

▶ 실행 컨텍스트의 종류

실행컨텍스트는 전역 실행 컨텍스트, 함수 실행 컨텍스트 두가지 유형이 존재

  • 전역 실행 컨텍스트: Javascript의 스크립트가 처음 실행될때 생성되며, Javscript에서의 전역 범위를 나타냄
  • 함수 실행 컨텍스트: 함수가 호출 될때마다 새로이 생성되며, 함수의 로컬범위를 나타냄

 

▶ 실행 컨텍스트의 단계

  1. 생성 단계 : Javascript 엔진은 실행 컨텍스트를 생성하고, 스크립트의 환경을 설정함 그리고 변수 및 함수의 값을 결정하고, 실행 컨텍스트에 대한 스코프 체인을 설정(즉, 실행 컨텍스트의 준비)
  2. 실행 단계 : Javascript 엔진은 실행 컨텍스트에서 코드를 실행하고, 스크립트의 모든 문 또는 식을 처리하고 모든 함수 호출에 대한 평가가 진행됨 (즉, 생성단계에서 준비된 코드를 실행하는 과정)

 

var n = 5;

function square(n) {
  var ans = n * n;
  return ans;
}

var square1 = square(n);
var square2 = square(8);  

console.log(square1)
console.log(square2)

 

해당 코드에서의 전역 실행 컨텍스트의 생성 단계

1. 전역실행 컨텍스트는 브라우저에서는 window, node 환경에서는 global이라는 활성객체를 생성

 

- 활성객체란

  • 전역실행 컨텍스트: 전역 범위에서 선언된 전역범수와 함수, 그리고 전역객체(window,global)에 대한 참조를 포함
  • 함수실행 컨텍스트: 해당 함수의 스코프에 기반하여 생성되며, 함수 내부에서 선언된 변수와 함수, 매개변수, this값등을 저장

2. 변수와 함수를 선언 및 초기화
현재 코드에서 사용하는 변수와 함수를 메모리에 저장하고, 변수는 undefiend로 초기화(위에 예시 var)

 

3. this값 설정

 

4. 스코프 체인 설정

현재 실행 컨텍스트의 스코프체인을 구성, 스코프 체인은 현재 실행컨텍스트의 활성 객체와 그 상위 스코프의 활성객체들로 구성

 

 

해당 코드에서의 전역 실행 컨텍스트의 실행 단계

1. 변수 할당

생성 단계에서 초기화된 변수들(var n)은 해당 값(5)를 할당

 

2. 코드 실행
전역 실행 컨텍스트에 있는 코드가 순차적으로 실행
함수 실행 구문을 만나면, 해당 코드에 대한 또 새로운 함수 실행 컨텍스트가 그안에서 생성되며, 해당 함수 실행 컨텍스트의 생성단계와 실행단계가 순차적으로 진행될때 까지 기다렸다가 실행을 멈추고, 함수 실행 컨텍스트의 실행이 완료되면,  다시 전역 실행 컨텍스트로 돌아가서 남아있던 전역 실행 컨텍스트를 실행

 


함수 실행 컨텍스트의 생성 단계

1. 활성 객체 생성
함수 square의 실행 컨텍스트가 생성되면, 해당 스코프 체인에 기반한 활성 객체인 함수 스코프의 활성객체가 생성


2. 변수 및 함수의 선언
함수 실행 컨텍스트 내에 사용되는 변수 및 함수 선언문을 파싱하고, 활성 객체 내부에 해당하는 변수와 함수를 등록

여기선 n과 ans가 함수의 활성객체에 등록

함수 실행 컨텍스트의 실행 단계

1. 변수 할당
var ans = n * n에서 n 변수의 값이 인자로 전달된 값으로 초기화되며, ans 변수의 계산 결과인 25가 할당

 

2. return문 실행
return에 의해 ans 변수의 값인 25가 반환되고 함수 실행 컨텍스트가 종료되고 소멸된 후에 다시 기존의 전역 실행 컨텍스트의 실행단계가 다시 시작

 

 

함수 실행 컨텍스트가 종료되고,

다시 전역 실행 컨텍스트로 돌아가서 남아있던 전역 실행 컨텍스트를 실행되는 과정

1. 변수 할당
var square1 = square(n); 구문에서 square 함수 호출 결과인 25가 square1 변수에 할당


2. 코드 실행
var square2 = square(8);를 만나 다시 또 위와 같이 함수 실행컨텍스트가 생성 되고 실행되며 반환된 값이 square2 변수에 할당되고, 최종적으로 console.log(square1) console.log(square2)에 대한 출력값이 출력

 

▶ 전역 실행 컨텍스트 vs 함수 실행 컨텍스트

전역실행 컨텍스트와 함수실행 컨텍스트는 엄연한 차이점이 존재


스코프

  • 전역 실행 컨텍스트는 전역 스코프를 구성하고, 스크립트 실행시 가장 먼저 생성
  • 함수 실행 컨텍스트는 함수 내부의 스코프를 구성하고, 다른 함수가 호출 될때마다 함수 스코프가 생성

활성 객체

  • 전역실행 컨텍스트는 전역 범위에서 선언된 전역범수와 함수, 그리고 전역객체(window,global)에 대한 참조
  • 함수실행 컨텍스트는 해당 함수의 스코프에 기반하여 생성되며, 함수 내부에서 선언된 변수와 함수, 매개변수, this값등을 저장

호출 시점과 동작

  • 전역실행 컨텍스트는 스크립트가 실행될때 생성되며, 코드의 실행이 끝까지 완료될때까지 유지
  • 함수실행 컨텍스트는 함수가 호출될때 생성되며, 함수의 실행이 완료되면 소멸. 함수를 호출할때마다 그 함수의 새로운 함수 실행 컨텍스트가 생성되므로, 서로 다른 함수 호출은 각각 독립된 함수 실행 컨텍스트를 생성하고 실행

 

 

 

참고

자바스크립트 실행 컨텍스트(Execution Context)란?

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

실행 컨텍스트(Execution Context)

댓글