실행 컨텍스트

heejung·2022년 4월 11일
0

deep dive

목록 보기
19/20
post-custom-banner

소스코드의 타입

전역 코드

  • 전역에 존재하는 소스코드
  • 전역 스코프 생성 => 전역 변수 관리
  • 전역 코드가 평가되면 전역 실행 컨텍스트 생성됨

함수 코드

  • 함수 내부에 존재하는 소스코드
  • 지역 스코프 생성 => 지역 변수, 매개변수, arguments 객체 관리
  • 함수 코드가 평가되면 함수 실행 컨텍스트 생성됨

eval 코드

  • eval 함수에 인수로 전달되어 실행되는 소스코드
  • strict mode에서 자신만의 독자적인 스코프 생성
  • eval 코드가 평가되면 eval 실행 컨텍스트 생성됨

모듈 코드

  • 모듈 내부에 존재하는 소스코드
  • 모듈별로 독자적인 모듈 스코프 생성
  • 모듈 코드가 평가되면 모듈 실행 컨텍스트 생성됨

소스코드의 평가와 실행

  1. 소스코드의 평가

    • 실행 컨텍스트 생성
    • 변수/함수 선언문 실행
    • 생성된 변수/함수 식별자를 실행 컨텍스트가 관리하는 스코프에 등록
  2. 소스코드의 실행

    • 선언문을 제외한 소스코드 순차적 실행 (런타임 시작)
    • 변수/함수의 참조를 스코프에서 검색하여 취득
    • 소스코드 실행 결과는 다시 스코프에 등록됨

var x;
x = 1;

위 예제의 처리 과정은 다음과 같다.

  1. 소스코드 평가 과정에서 변수 선언문 var x; 먼저 실행
  2. 생성된 변수 식별자 x => 실행 컨텍스트가 관리하는 스코프에 등록된 후 undefined로 초기화
  3. 변수 할당문 x = 1; 실행 => 스코프에 x 변수가 등록되어 있는지 확인 후 할당
  4. 할당 결과를 실행 컨텍스트에 등록하여 관리

실행 컨텍스트의 역할

아래 예제를 자바스크립트 엔진이 어떻게 평가하고 실행하는지 살펴보자.

// 전역 변수 선언
const x = 1;
const y = 2;

// 함수 정의
function foo(a) {
  // 지역 변수 선언
  const x = 10;
  const y = 20;
  
  // 메소드 호출
  console.log(a + x + y); // 130
}

// 함수 호출
foo(100);

// 메소드 호출
console.log(x + y); // 3

  1. 전역 코드 평가

    • 전역 변수/함수 선언문 먼저 실행
    • 생성된 전역 변수 x, y와 전역 함수 foo를 전역 스코프에 등록
  2. 전역 코드 실행

    • 전역 변수 x, y에 값이 할당되고 전역 함수 foo가 호출됨
    • 전역 함수 foo가 호출되면 전역 코드의 실행을 일시 중단하고 함수 내부로 진입
  3. 함수 코드 평가

    • 매개변수, 지역 변수 선언문 먼저 실행
    • 생성된 매개변수 a, 지역 변수 x, y를 지역 스코프에 등록
    • arguments 객체가 생성되어 지역 스코프에 등록
    • this 바인딩 결정
  4. 함수 코드 실행

    • 매개변수 a와 지역 변수 x, y에 값이 할당됨
    • console.log 메소드 호출됨
    • 인수로 전달된 a+x+y 평가 => 스코프 체인을 통해 식별자 검색하여 참조
    • 함수 종료 후 다시 전역 코드 실행

코드가 실행되려면 스코프, 식별자, 코드 실행 순서 등의 관리가 필요하고, 이 모든 것을 관리하는 게 바로 실행 컨텍스트다.

실행 컨텍스트는 식별자를 등록/관리하는 스코프코드 실행 순서 관리를 구현한 내부 메커니즘이다.
모든 코드는 실행 컨텍스트를 통해 실행되고 관리된다.

  • 식별자와 스코프 => 렉시컬 환경으로 관리
  • 코드 실행 순서 => 실행 컨텍스트 스택으로 관리

실행 컨텍스트 스택

  • 소스코드 평가 => 실행 컨텍스트 생성
  • 실행 컨텍스트는 스택 자료구조로 관리됨
  • 코드의 실행 순서 관리
  • 실행 컨텍스트 스택의 최상위 => 현재 실행 중인 코드의 실행 컨텍스트가 위치

const x = 1;

function foo() {
  const y = 2;
  
  function bar() {
    const z = 3;
    console.log(x + y + z);
  }
  bar();
}

foo(); // 6

  1. 전역 코드의 평가와 실행

    • 전역 코드 평가 => 전역 실행 컨텍스트 생성 후 실행 컨텍스트 스택에 push
    • 전역 변수 x, 전역 변수 foo => 실행 컨텍스트에 등록
    • 전역 코드 실행 => 전역 변수 x에 값이 할당되고 전역 함수 foo 호출
  2. foo 함수 코드의 평가와 실행

    • foo 함수 코드 평가 => foo 함수 실행 컨텍스트 생성 후 실행 컨텍스트 스택에 push
    • 지역 변수 y, 중첩 함수 bar => 실행 컨텍스트에 등록
    • 함수 코드 실행 => 지역 변수 y에 값이 할당되고 중첩 함수 bar 호출
  3. bar 함수 코드의 평가와 실행

    • bar 함수 코드 평가 => bar 함수 실행 컨텍스트 생성 후 실행 컨텍스트 스택에 push
    • 지역 변수 z => 실행 컨텍스트에 등록
    • 함수 코드 실행 => 지역 변수 z에 값이 할당되고 console.log 메소드 호출
    • bar 함수 종료
  4. foo 함수 코드로 복귀

    • bar 함수 실행 컨텍스트 => 실행 컨텍스트 스택에서 pop하여 제거
    • foo 함수 종료
  5. 전역 코드로 복귀

    • foo 함수 실행 컨텍스트 => 실행 컨텍스트 스택에서 pop하여 제거
    • 전역 실행 컨텍스트 => 실행 컨텍스트 스택에서 pop하여 제거

렉시컬 환경

  • 실행 컨텍스트를 구성하는 컴포넌트
  • 식별자, 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록하는 자료구조 (식별자, 스코프 관리)
  • EnvironmentRecord, OuterLexicalEnvironmentReference 로 구성

EnvironmentRecord (환경 레코드)
- 스코프에 포함된 식별자 등록
- 식별자에 바인딩된 값 관리

OuterLexicalEnvironmentReference (외부 렉시컬 환경에 대한 참조)
- 상위 스코프를 가리킴
- 스코프 체인 (단방향 링크드 리스트) 구현


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

var x = 1;
const y = 2;

function foo(a) {
  var x = 3;
  const y = 4;
  
  function bar(b) {
    const z = 5;
    console.log(a + b + x + y + z);
  }
  bar(10);
}

foo(20); // 42

1. 전역 객체 생성

  • 빌트인 전역 프로퍼티/함수, 표준 빌트인 객체, 클라이언트 사이드 Web API, 호스트 객체 추가됨
  • Object.prototype 을 상속받음 (프로토타입 체인의 일원)

2. 전역 코드 평가

  1. 전역 실행 컨텍스트 생성 => 실행 컨텍스트 스택에 push

  2. 전역 렉시컬 환경 생성 => 전역 실행 컨텍스트에 바인딩

    2-1. 전역 환경 레코드 생성

    • 객체 환경 레코드 생성 => BindingObject 객체 (전역 객체) 와 연결
      - var로 선언한 변수, 함수 선언문으로 정의한 함수 => 전역 객체의 프로퍼티/메소드가 됨

    • 선언적 환경 레코드 생성
      - let, const로 선언한 변수, 함수 표현식으로 정의한 함수 등록/관리

    2-2. this 바인딩

    • 전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this 바인딩 (전역 객체)
    • 전역 환경 레코드, 함수 환경 레코드에만 존재 (객체 환경 레코드, 선언적 환경 레코드에는 X)

    2-3. 외부 렉시컬 환경에 대한 참조 결정

    • 상위 스코프를 가리킴
    • 전역 코드라면 null (전역 렉시컬 환경 = 스코프 체인의 종점에 존재)


3. 전역 코드 실행

  • 전역 실행 컨텍스트의 전역 렉시컬 환경에서 식별자 검색 => 식별자 결정
  • 전역 변수 x, y에 값 할당
  • foo 함수 호출

4. foo 함수 코드 평가

  1. 함수 실행 컨텍스트 생성

  2. 함수 렉시컬 환경 생성 => foo 함수 실행 컨텍스트에 바인딩

    2-1. 함수 환경 레코드 생성

    • 매개변수, arguments 객체, 함수 내부의 지역 변수나 중첩 함수 등록/관리

    2-2. this 바인딩

    • 함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this 바인딩 (함수 호출 방식에 따라 결정)

    2-3. 외부 렉시컬 환경에 대한 참조 결정

    • 함수의 상위 스코프 => 함수 객체의 내부 슬롯 [[Environment]]에 저장됨


5. foo 함수 코드 실행

  • foo 함수 렉시컬 환경에서 식별자 검색 => 없으면 스코프 체인 타고 상위 스코프에서 검색
  • 매개변수에 인수 할당
  • 지역 변수 x, y에 값 할당
  • bar 함수 호출

6. bar 함수 코드 평가

  • foo 함수 코드 평가와 동일한 과정


7. bar 함수 코드 실행

  • 매개변수에 인수 할당
  • 지역 변수 z에 값 할당
  • console.log(a + b + x + y + z); 실행
    • console 식별자 검색
      (전역 렉시컬 환경 - 객체 환경 레코드의 BindingObject를 통해 전역 객체에서 찾을 수 있음)
    • log 메소드 검색 (console 객체가 직접 소유하는 프로퍼티)
    • a, b, x, y, z 식별자 검색 => 표현식 평가
    • 평가 후 생성한값을 console.log 메소드에 전달하여 호출

8. bar 함수 코드 실행 종료

  • 실행 컨텍스트 스택에서 bar 함수 실행 컨텍스트 제거
  • 이때 bar 함수 렉시컬 환경은 소멸하지 않음 (실행 컨텍스트에 의해 참조되지만 독립적인 객체)

9. foo 함수 코드 실행 종료

  • 실행 컨텍스트 스택에서 foo 함수 실행 컨텍스트 제거

10. 전역 코드 실행 종료

  • 실행 컨텍스트 스택에서 전역 실행 컨텍스트 제거

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

let x = 1;

if (true) {
  let x = 10;
  console.log(x); // 10
}

console.log(x); // 1
  1. 코드 블록 실행
  2. 선언적 환경 레코드를 갖는 렉시컬 환경 새롭게 생성 => 기존 렉시컬 환경 교체
  3. 새로 생성된 블록 렉시컬 환경의 외부 렉시컬 환경에 대한 참조 => 이전의 렉시컬 환경을 가리킴
  4. 코드 블록 종료 => 코드 블록 실행 이전의 렉시컬 환경으로 복귀


블록 레벨 스코프를 생성하는 모든 블록문에 적용이 되며, for문 같은 경우는 코드 블록이 반복해서 실행될 때마다 매번 새로운 렉시컬 환경이 생성된다.
profile
프론트엔드 공부 기록
post-custom-banner

0개의 댓글