JS Deep Dive | 2021.12.21

invidia·2021년 12월 21일
0

TIL

목록 보기
11/29

23. 실행 컨텍스트

정리

소스코드의 타입

  • 전역 코드: 전역 스코프를 생성하여 전역 실행 컨텍스트에서 관리
  • 함수 코드: 지역 스코프를 생성하여 함수 실행 컨텍스트에서 관리
  • eval 코드: 독자적인 스코프를 생성 (strict mode)
  • 모듈 코드: 모듈별로 독립적인 스코프를 생성

평가와 실행

평가단계

  1. 컨텍스트 생성
  2. 선언문 실행 후 실행 컨텍스트가 관리하는 스코프에 등록

실행단계

  1. 참조: 스코프에 검색해 취득
  2. 값의 변경: 스코프에 등록

Example

  1. var x;
    • 스코프에 등록
    • unde로 초기화
  2. x = 1;
    • 스코프에 등록되어있는 지 확인
    • 값을 할당
    • 할당 결과를 스코프에 등록

실행 컨텍스트의 역할

const a = 1;
const b = 2;
function add(n) {
    const a = 11;
    const b = 22;
    console.log(n+a+b); // 66
}
add(33);
console.log(a+b) // 3

의 실행과정을 살펴보면

  1. 전역 코드 평가
    • a,b,add()를 실행하여 전역 스코프에 등록
  2. 전역 코드 실행
    • 값의 할당(a,b) -> 함수 호출(add(33))
    • 함수 호출될 때, 전역 코드 실행 중단
    • 함수 내부로 진입
  3. 함수 코드 평가
    • 매개변수, 선언문 실행
    • arguments 객체 생성
    • this 바인딩 결정
  4. 함수 코드 실행
    • 매개 변수에 값이 할당
    • console.log 메서드 호출
      • console을 스코프에서 검색
        • 지역 스코프에서 찾지 못함
        • 지역 스코프 -> 전역 스코프
        • 전역 스코프의 전역 객체에서 console을 찾음
    • console 객체의 프로토타입 체인을 통해 log 메서드를 검색 후 n+a+b 실행
      • n,a,b 식별자는 스코프 체인을 통해 검색
    • 과정이 종료되면 전역 코드 실행을 계속
  • 이처럼 코드 실행을 위해 필요한 것들은 다음과 같다.

    • 생성된 모든 식별자를 스코프 구분하여 등록 및 상태 변화를 지속적으로 관리
      • 전역 객체의 프로퍼티를 전역 스코프에서 검색
    • 중첩 관계속에서 스코프 체인을 형성하여 식ㅠㅎ흏ㅍ퓨ㅗㅜㅠㅜㅠ별자 검색
    • 코드의 실행순서를 변경 및 되돌아갈 수 있어야 함.
  • 이러한 것들을 관리하는 것이 실행 컨텍스트이다.

    • 실행 컨텍스트소스코드 실행하는데 필요한 환경 제공 및 코드의 실행결과를 관리한다.
      • 코드 실행순서실행 컨텍스트 스택으로 관리
      • 식별자 스코프렉시컬 환경으로 관리

실행 컨텍스트 스택

  • Stack 자료구조를 이용하여 코드 실행 순서를 관리한다.

  • 전역 -> foo() -> bar()을 호출했다고 치면

    1234567
    bar 함수 실행 컨텍스트
    foo 함수 실행 컨텍스트foo 함수 실행 컨텍스트foo 함수 실행 컨텍스트
    전역 실행 컨텍스트전역 실행 컨텍스트전역 실행 컨텍스트전역 실행 컨텍스트전역 실행 컨텍스트
    • 이런식으로 Stack이 동작한다.
      • 가장 상위에 있는 컨텍스트에게 제어권이 있으며, 이 컨텍스트를 Running executuion context(실행중인 실행 컨텍스트) 라고 한다.

렉시컬 환경

  • 스코프와 식별자를 관리
  • Key Value꼴의 객체형태로 된 스코프를 생성하여 식별자(Key) - 식별자에 바인딩된 값(Value)를 관리하는 저장소
  • Lexical Environment
    • 환경 레코드: 식별자와 바인딩된 값을 관리하는 저장소
    • 외부 렉시컬 환경에 대한 참조: 상위 스코프를 가리킨다. (이를 이용해 스코프 체인을 구성)

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

  • 전역 -> foo() -> bar()
  1. 전역 객체 생성

    • 빌트인 전역 프로퍼티 & 전역 함수 등을 갖고 있는 전역 객체를 생성
    • 전역 객체 역시도 프로토타입 체인의 일원
  2. 전역 코드 평가

    1. 전역 실행 컨텍스트 생성
      • 실행 컨텍스트 스택에 푸시
    2. 전역 렉시컬 환경 생성
      1. 전역 환경 레코드 생성
        • 객체 환경 레코드 - BindingObject(전역 객체)와 연결 - var로 선언된 전역 변수 & 전역 함수는 전역 환경 레코드 - 객체 환경 레코드 - binding object를 통해 전역 객체의 프로퍼티와 메서드가 된다. - 전역 객체 식별자는 생략가능 - 선언적 환경 레코드(const, let으로 선언한 전역변수)로 구성
        • var 전역 변수전역 함수let const 전역 변수
          평가 단계key로 등록 -> undefined로 초기화key로 등록 -> 함수 객체를 생성하여 할당key로 등록
          실행 단계값을 등록초기화
      2. this 바인딩: 전역 환경 레코드의 [[GlobalThisVAlue]] 슬롯에 this가 바인딩
        • 객체 & 선언적 환경 레코드에는 this 바인딩 X
      3. 외부 렉시컬 환경에 대한 참조 결정
        • 최상위 전역이므로 null이 할당 (최종점)
    3. 전역 코드 실행
      • 식별자 결정(실행중인 실행 컨텍스트 -> 상위 컨텍스트 -> ...)
    4. 함수 평가
  3. foo함수 코드 평가

    1. 함수 실행 컨텍스트 생성
    2. 함수 렉시컬 환경 생성
      1. 함수 환경 레코드 생성
        • 매개변수, arguments 객체, 내부 지역 변수 & 중첩 변수 등록 관리
      2. this 바인딩: 일반함수로 호출 -> 전역 객체 가르킴
      3. 외부 렉시컬 환경에 대한 참조 결정
        • 정의된 전역 스코프가 할당
    3. 함수 코드 실행
      • 이 때 식별자 결정은 (foo -> 전역 순으로 이루어짐)
    4. bar 함수 평가
  4. bar함수 코드 평가

    • foo함수와 비슷하며 외부 렉시컬 환경에 대한 참조가 foo함수의 환경으로 결정된다.
    • bar에서 실행된 console.log는 스코프체인 bar -> foo -> 전역에 따라 검색한다.
  5. bar 함수 코드 실행 종료

    • 이 때, 누군가가 참조학 있다면 환경은 소멸되지 않는다. (스택에선 제거됨.)
  6. foo 함수 코드 실행 종료

  7. 전역 코드 실행 종료

느낀점

  • JS 엔진이란 뭘까? 가 궁금해서 찾아본 글 (https://oowgnoj.dev/review/advanced-js-1)

    • 이에 따르면 JS가 인터프리터 언어이므로 엔진 내부에서 컴파일과정이 일어나는데, 이 때 최적화가 일어난다고 한다.
      • 책을 읽으며 다른 언어에서는 엔진이라는 표현을 못들어본 것 같았는데, JS에서는 엔진이 ~~를 한다. 라는 내용이 많아 왜 JS에만 엔진이 있는지 그 역할은 무엇인지 궁금했는데 컴파일러와 비슷한 역할을 하고 인터프리터 언어인 JS에서 컴파일러 비슷한 역할을 한다는 것을 이해했다.
  • 실행 컨텍스트 부분은 단어가 굉장히 난해하고 구조도 복잡하다. 하지만 결론만큼은 간단하다고 생각한다.

    • 본인이 정의된 지점이 상위 스코프이며, 이는 기억된다.
    • 본인이 속한 환경에서 식별자를 검색한다.
    • 찾지 못했다면, 스코프 체인을 이용하여 상위 스코프에서 식별자를 검색한다.
    • 즉 단방향 링크드 리스트에서의 검색이다. 값은 환경 레코드이며, 가리키는 포인터는 외부 렉시컬 환경이다.
function ExecutionContext(environmentRecord, outer) {
  this.environmentRecord = environmentRecord;
  this.outer = outer;

  this.findProperty = function findProperty(property) {
    const foundValue = this.environmentRecord[property];
    if (!foundValue && !this.outer) return null;
    return foundValue ? foundValue : this.outer.findProperty(property);
  };
}

const globalExecutionContext = new ExecutionContext({ x: 1 }, null);
const fooExecutionContext = new ExecutionContext(
  { y: 2 },
  globalExecutionContext
);
const barExecutionContext = new ExecutionContext(
  { z: 3 },
  fooExecutionContext
);

console.log(barExecutionContext.findProperty("x")); // 1

전역 -> foo -> bar에서
전역의 x를 bar에서부터 찾는 동작을 코드로 나타냈다.

0개의 댓글