자바스크립트 실행 환경(Execution Context)

hoo00nn·2020년 12월 31일
3
post-thumbnail

이번 포스팅에선 자바스크립트 실행 환경에 대해서 알아보겠다.

실행 환경 (Execution Context) 란?

자바스크립트에서 말하는 실행 컨텍스트란, 실행 가능한 코드가 실행되기 위해 필요한 환경으로 이해할 수 있다.

실행 환경(Execution Context)는 다음과 같은 프로퍼티를 갖는다.

  • Variable Environment
  • Lexical Environment

Variable Environment

Variable Environment 세 가지 프로퍼티를 가지고 있다.

바로 environment recordouter environment reference 그리고 ThisBinding 이다.

environment record

  • 현재 문맥의 식별자 정보 (변수나 함수 등)
  • 변수 객체(VO)

ThisBinding

this 의 값이 여기서 결정된다. 글로벌 실행 컨텍스트에서 this 는 global object 이다.

함수 실행 컨텍스트에서는 this 값은 어떻게 함수가 호출되었는지에 따라 달라진다. 만약 함수가 object reference로 호출되었다면 this 는 해당 객체를 가리키게 된다. 그렇지 않으면 this 는 글로벌 객체(window)를 가리키거나 strict mode에서는 undefined 를 가리키고 있다.

outer environment reference

  • 상위 Lexical Environment를 참조하는 포인터
  • 중첩된 자바스크립트 코드에서 스코프 탐색을 하기 위해 사용

그럼 outer environment가 어떤 형태로 존재하여 상위 스코프를 탐색할 수 있는지 살펴보겠다.

// global
const globalA = 'globalA';

function foo() {
  const fooA = 'fooA';

  function bar() {
    const barA = 'barA';

    console.log(globalA);   // globalA
    console.log(fooA);      // fooA
    console.log(barA);      // barA
    console.log(unknownA);  // Reference Error
  }

  bar();
}

foo();

위의 예제 코드가 있고, 각 코드의 실행 환경에 따른 environment를 살펴보면 다음과 같다.

GlobalEnvironment = {
  // Global Environment Record에는
  // Object Environment Record와 Declarative Environment Record 등이 같이 존재하지만 이 글에서는 구분하지 않겠다.
  environmentRecord: {
    globalA: 'globalA'
  },
  outer: null
	ThisBinding: <Global Object>
};

fooEnvironment = {
  environmentRecord: {
    fooA: 'fooA'
  },
  outer: globalEnvironment // foo는 Global에서 생성됐다.
	ThisBinding: <Global Object>
}

barEnvironment = {
  environmentRecord: {
    barA: 'barA'
  },
  outer: fooEnvironment // bar는 foo 안에서 생성됐다.
	ThisBinding: <Global Object>
}

bar의 environment에서는 fooAglobalA 를 찾을 수 없기때문에 outer참조를 통해 상위 environment로 올라가 식별자를 찾아간다. outernull 임에도 불구하고 unknownA 처럼 찾을 수 없는 식별자라면 Reference Error가 발생한다.

Lexical Environment

Lexical Environment는 Variable Environment와 동일하게 environment recordouter environment reference 그리고 ThisBinding 를 가지고 있다.

그럼 Lexical Environment 와 Variable Environment 의 차이점이 뭘까?

Lexical Environment와 Variable Environment의 차이점은 다음과 같다.

  • Variable environment :
    • Environment Record: 현재 실행 컨텍스트 내에서 호이스팅이 되는 애들(var, 함수선언문)등을 저장
    • outer Environment Reference : outer environment
  • Lexical environment :
    • Environment Record: let,const로 선언된 변수, 함수표현식도 포함.
    • outer Environment Reference : Variable environment

위의 내용으로는 이해가 잘 안될 수 있다. 코드를 한 번 살펴보겠다.

아래의 코드에서 do_something 함수를 호출하면 실행 컨텍스트가 생길 것이다.

  function do_something() {
     var a = 1;
     let b = 2;
     while (true) {
         var c = 3;
         let d = 4;
         console.log(b);
         break;
}
}

do_something();
ExecutionContext:
    LexicalEnvironment:
        b -> nothing
        outer: VariableEnvironment 
    VariableEnvironment:
        a -> undefined, c -> undefined
        outer: global
    ...

또한 while문에 진입하게 되면 새로운 컨텍스트가 생긴다.

ExecutionContext:
    LexicalEnvironment:
        d -> nothing
        outer:
            LexicalEnvironment
                b -> 2
                outer: VariableEnvironment
    VariableEnvironment:
        a -> 1, c -> undefined
        outer: global
    ...

while문이 종료되면 원래의 실행 컨텍스트로 복원된다.

ExecutionContext:
    LexicalEnvironment
        b -> 2
        outer: VariableEnvironment
    VariableEnvironment:
        a -> 1, c -> 3
        outer: global

기본값 매개변수(Default parameter)와 Lexical Environment

아래의 코드가 에러가 나는 이유는 뭘까?

바로 letconst 의 TDZ(Temporal dead zone) 때문이다.

TDZ의 대한 설명은 여기를 클릭하면 볼 수 있다.

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

function foo(a, b = add(a, 1)) {
  return `foo ${a + b}`;
}

function bar(a = add(b, 1), b) {
  return `bar ${a + b}`;
}

console.log(foo(1)); // foo 3
console.log(bar(undefined, 1)); // Error

bar 함수의 매개변수 a에서 b의 참조를 찾을 수 없기 때문에 에러가 난다. TDZ를 이해했다면 당연한 내용일 것이다.

하지만 여기서 정말 중요한 부분은 바로 Lexical Environment를 새로 만든다는 것이다.

무슨 말인지 이해하기 어려울 수도 있기 때문에 코드 하나를 더 보면서 설명하겠다.

const str = 'outerText';

function foo(fn = () => str) {
  const str = 'innerText';

  console.log(fn());
}

foo(); // 'outerText'

만약 아래의 코드가 새로운 Lexical Environment를 만들지 않았을 때의 상황을 생각해보겠다. (앞의 과정은 생략하고 foo함수 호출부터 설명)

  1. foo() 함수 호출
  2. foo 컨텍스트 생성되고 Lexical Environment에 str 변수와 fn함수 저장
  3. fn() 함수 호출
  4. fn 컨텍스트 생성되고 str변수를 리턴 하기위해 자신의 Lexical Environment 확인
  5. 자신의 Lexical Environment에 없다면, 상위 Lexical Environment인 foo 컨텍스트에서 str변수를 찾음
  6. innerText 출력

하지만 실제 코드를 실행시켜보면 outerText를 출력한다. 이는 상식적이지 않은 동작이고, 마치 함수 외부에서 내부 스코프를 참조하고 변경시켜버리는 모양이 되어버리고 여러 문제를 일으킬 수 있는 여지가 된다. 이 때문에 기본값 매개변수는 함수의 내부를 참조할 수 없도록 만들어야 한다. 그러기 위해서는 매개변수, 함수 내부 변수들을 Environment부터 분리해서 추가적인 스코프 체인을 만들어야 한다.

따라서 함수가 실행되고 변수들을 초기화할 때는 다음과 같은 동작을 한다.

함수가 호출될 때, 매개변수들을 먼저 초기화하고 기본값 매개변수가 있다면 새로운 Environment를 추가로 만들어서 여기에 함수 내부의 변수들을 등록한다. 이렇게 Environment를 새로 만들어버리기 때문에 기본값 매개변수는 함수 내부를 참조할 수 없으면서 함수 내부에서는 매개변수를 참조할 수 있는 중첩 구조를 만들어낼 수 있다.

요약

실행 컨텍스트는 다음과 같은 경우에 생성된다.

  • 전역공간에서
  • 함수 호출 시
  • { } 코드 블럭 사용 시 (블록 스코프)
  • eval 함수 사용 시 (잘 쓰이지 않고, 지양하는 코딩 방법)

실행 컨텍스트는 두 가지 단계로 생성된다.

  • Compilation Phase
  • Execution Phase

Creation Phase에서 하는 일

  • LexicalEnvironment 컴포넌트 생성
  • VariableEnvironment 컴포넌트 생성

Execution Phase에서 하는 일

  • 자바스크립트 엔진이 한줄 한줄 위에서 부터 코드를 읽으면서 코드를 실행
  • 변수들이 값에 할당
profile
😀 신기술에 관심이 많고, 함께 성장하고 함께 개발하고 싶은 개발자가 되고 싶습니다. 😀

0개의 댓글