[JS] 실행 컨텍스트 (Execution Context)

jiseong·2021년 8월 3일
2

T I Learned

목록 보기
18/291

실행 컨텍스트 (Execution context)

Execution context는 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념으로 쉽게 생각하자면, 코드가 실행되기 위해 필요한 환경으로 이해하면 된다.

  • 해당 환경에는 다음과 같은 정보가 필요하며 이러한 정보들을 형상화하고 구분하기 위해 객체의 형태로 생성하고 관리한다.

    • 변수: 전역변수, 지역변수, 매개변수, 객체의 프로퍼티
    • 함수 선언
    • 변수의 유효범위(scope)
    • this
  • 실행 가능한 코드

    • 전역 코드 : 전역 영역에 존재하는 코드
    • Eval 코드 : eval 함수로 실행되는 코드
    • 함수 코드 : 함수 내에 존재하는 코드
  • 모든 실행 가능한 코드는 다음 과정을 거친다.

    1. 코드의 평가 과정
      해당 과정에서 실행 컨텍스트가 생성된다. 변수, 함수, 클래스 등의 선언문이 우선 평가되고 그 결과가 실행 컨텍스트에 등록된다.

    2. 코드의 실행
      선언문을 제외한 코드를 순차적으로 실행한다. 이 때의 과정이 곧 런타임을 의미한다.

실행 컨텍스트 타입 (Types of Exection Context)

  • 전역 실행 컨텍스트(Global Execution Context)
  • 함수 실행 컨텍스트(Function Execution context)
  • Eval Function Execution Context(잘 쓰이지 않음)

호출 스택(call stack)

우선, Global Execution Context 와 Function Execution Context를 추적 할 순서를 기억하기 위해서 자바스크립트 엔진은 call stack이라는 Data structure를 사용한다. ( LIFO )

📝 예시코드

let a = 'Hello World!';

function first() {
  second();
}

function second() {
  let b = 'second';
}

first();

  1. 자바스크립트 엔진이 처음 코드를 실행하게 되면 전역 실행 컨텍스트(Global Execution Context)가 생성되고 call stack에 push된다. (전역 실행 컨텍스트는 페이지가 종료될 때까지 유효)

  2. first함수를 호출 할 때, first의 함수 실행 컨텍스트(Function Execution context)를 생성하고 현재 stack에 push한다.

  3. first함수 내에서 second함수를 호출 할 때, second의 함수 실행 컨텍스트(Function Execution context)를 생성하고 현재 stack에 push한다.

  4. second함수의 실행이 완료되면, stack에서 second의 함수 실행 컨텍스트(Function Execution context)를 pop한다.

  5. first함수의 실행이 완료되면, stack에서 first의 함수 실행 컨텍스트(Function Execution context)를 pop한다.

  6. 모든 코드가 실행되고나면, 자바스크립트 엔진은 stack에서 전역 실행 컨텍스트(Global Execution Context)를 제거한다.


그렇다면 자바스크립트 엔진은 어떤 방식으로 실행 컨텍스트를 생성할까?

실행 컨텍스트 (Execution context)의 구조와 생성


우선, 실행 컨텍스트 생성 과정을 보기전에 구조를 파악해보자.

✔️ 실행 컨텍스트의 구조

  • [ES6기준] 실행 컨텍스트의 추상적인 구조는 크게 1)Lexical Environment2)Variable Environment로 나뉜다.

1) Lexical Environment

  • Lexical Environment는 블록의 유효범위 안에 있는 변수와 함수 식별자를 매핑하는데 사용된다. (식별자(identifier)란 참조 대상 식별자로써 함수와 변수의 이름과 같이 어떤 대상을 다른 대상과 구분하여 식별할 수 있는 유일한 이름이다.)

  • 함수와 변수를 {name: value}형태로 저장한다.

  • Lexical Environment는 세가지 일을 한다. (VariableEnvironmnet도 동일)

    • 1.1) 환경 레코드(Environment Record)
    • 1.2) 외부 환경 참조(Outer Environment Reference)
    • 1.3) this 바인딩(this binding)

1.1) Environment Record(ER)

  • Environment Record는 Lexical Environment에 변수와 함수 식별자의 바인딩을 기록하는 객체이다.

  • function의 경우에는 arguments가 포함된다.

  • Environment Record는 또 다시 1.1.1)Declarative environment record1.1.2)Object environment record으로 나뉜다.

    1.1.1) Declarative environment record
    • 변수 선언 및 함수 선언, Try문의 Catch 절에서 사용되는 식별자들의 바인딩을 관리한다.
    1.1.2) Object environment record
    • 글로벌 함수와 변수에 사용.

    • with문과 같이 식별자를 특정 객체 속성으로 취급할 때 사용되며, 이를 위해 bindingObject라는 프로퍼티에 바인딩한다.

1.2) Outer Environment Reference(OER)

  • 식별자(identifier) 검색을 위해 외부 Lexical Environment를 참조하는 포인터로 Lexical nesting structure(scope Chain)에서 스코프 탐색을 위해 사용

    • Global Lexical Environment에서는 null

    • Function Lexical Environment에서는 상위 실행환경의 Lexical Environment를 참조

Function Lexical Environment에서 상위 lexical environment를 참조한다는 의미는 다음과 같다.

function a(){
    var myVar = 2;
    b();
}
function b(){
    console.log(myVar);
}
var myVar = 1;
a();  // ?

위의 코드를 보면 2가 찍힐 줄 알았는데 1이 나오게된다.
이유는 호출 시점에 실행 컨텍스트가 생성되는 것은 맞지만 Outer Environment Reference를 정의할 때 호출 시점의 상위 lexical environment가 아니라 선언된 시점의 상위 lexical environment를 참조한다고 생각하면 쉽다. 이는 렉시컬 스코프(lexical scope)라고도 한다.

https://jsbeginners.com/understanding-the-weird-parts-notes-1/

추가적인 예시코드로 아래를 실행해보면 이해하는데 도움이 될 것이다.

function one(){
    var a = 5;
    var b = 10;
    two();
}
function two(){
    console.log(a);
    console.log(b);
}

var a = 1;
one();

1.3) This Binding

  • 'this'값 결정 (브라우저의 경우 글로벌에서는 Window를 가리키며 함수에서의 this 값은 어떻게 함수가 호출되었는지에 따라 달라진다.) 📌 this관련하여 추후작성

2) Variable Environment

  • Lexical Environment와 동일하게 식별자들의 바인딩에 대한 정보를 가지고 있다.

  • 초기값을 복원할 때 사용하기 위해 만들어졌다. (Lexical Environment의 값은 실행 중에 변경될 수도 있다.)

  • LexicalEnvironment에서는 let과 const의 바인딩을 저장하며 VariableEnvironment에서는 var에 대해서 저장한다.


✔️ 실행 컨텍스트의 생성

위에서 실행 컨텍스트의 구조를 알아보았고 예시코드를 통해 실제로 어떻게 실행 컨텍스트가 생성되는지 알아보자.

📝 예시 코드

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
 var g = 20;
 return e * f * g;
}

c = multiply(20, 30);

전역 코드에 진입하면 전역 코드를 한 줄씩 실행하기 이전에 전역 실행 컨텍스트가 아래와 같은 형태로 생성이 된다.

1. 전역 코드 평가

GlobalExectionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: < uninitialized >,
      b: < uninitialized >,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}
  • LexicalEnvironment에 함수와 let, const 변수를 매핑했지만 let, const로 선언된 변수의 초기값은 할당되지 않았다.(이는 아직 메모리 공간 확보가 안되었다는 의미)

  • VariableEnvironment에 var로 선언된 변수는 메모리에 매핑되며 초기값으로 undefined 할당

  • 글로벌에서 Outer Environment Reference는 null을 가리킨다.

  • 글로벌에서 ThisBinding는 Global Object 즉, Window이다.

전역 코드 평가가 끝나고나면, 전역 코드를 순차적으로 실행한다.
이 때, 위에서 선언되었던 변수들에 실제 값을 할당한다.

2. 전역 코드 실행

GlobalExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      a: 20,
      b: 30,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // Identifier bindings go here
      c: undefined,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}
  • let, const로 선언된 변수의 초기값은 여기서 할당된다.

multiply(20, 30) 함수가 호출되는 시점에 전역 코드의 실행이 멈추고 호출된 함수 내부로 진입하며 함수 실행 컨텍스트를 아래와 같은 형태로 생성한다.

3. 함수 코드 평가

FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined(strict mode)>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: undefined
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined(strict mode)>
  }
}
  • 함수 실행 컨텍스트의 Environment Record에는 arguments가 포함되어 있다.

  • Outer Environment Reference는 상위 scope인 GlobalLexicalEnvironment를 가리킨다.

  • ThisBinding는 어떻게 함수가 호출되었는지에 따라 달라지는데 여기서는 여전히 Window이다.


multiply(20, 30) 함수 코드 평가가 끝나고나면, 마찬가지로 함수 코드를 순차적으로 실행하며 변수들에 실제 값을 할당한다.

4. 함수 코드 실행

FunctionExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      Arguments: {0: 20, 1: 30, length: 2},
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>,
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // Identifier bindings go here
      g: 20
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

multiply(20, 30) 함수 코드가 순차적으로 실행되고 종료되면 call stack에서 pop되며 전역 실행 컨텍스트의 C 값이 업데이트 된다. 그 후 다음의 전역 코드를 실행한다.

그 후 전역 코드가 종료되면 call stack에서 전역 실행 컨텍스트를 pop하며 프로그램이 종료된다.


나만의 방식으로 테스트

var a = 5;

function test(){

    console.log(a);
    console.log(b);
}

test();

var b = 5;

let a = 5;

function test(){

    console.log(a);
    console.log(b);
}

test();

let b = 5;

var는 평가 과정에서 이미 undefined로 초기화되었지만 let은 평가 과정에 매핑만 할뿐 초기화는 해주지 않았다. 그리고 함수가 실행되는 시점에서 두 코드다 변수 a는 실행 과정까지 거쳐 실제 값이 할당 되었지만 변수 b는 실제 값이 할당되지 않은 상태이기 때문에 let b에서 ReferenceError가 발생하는 것이다.

/ 추가적으로 코드 블럭일때도 실행 컨텍스트가 생성되는지 궁금하다... /
function callFunc() {
let a = 5;

{ // 이때도 실행 컨텍스트가 생기는가?
    let a = 100;
    console.log(a);
}
console.log(a);

}

callFunc();


Reference

0개의 댓글