본문 바로가기
웹(web)/프론트엔드-javascript

자바스크립트 동작 방식: 실행 컨텍스트(execution context)

by 바코94 2021. 5. 2.

자바스크립트는 인터프리터 방식의 언어일까? 답은 no에 가깝다고 할 수 있다. 인터프리터 방식의 언어라면 다음 코드는 에러가 나야할 것이다.

foo();
function foo(){console.log(1)}

 

코드는 크게 선언문과 실행문으로 나누어 볼 수 있다. 선언문은 변수, 함수 등에 대한 식별자 이름을 만드는 것이고 ( const a, function foo() {} ) 실행문은 선언된 것들을 가지고 사용하는 부분으로 볼 수 있다. (console.log(a), foo()). 결국, 자바스크립트 파일은 선언문과 실행문의 묶음이다. 이를 실행하는 규칙을 방식이 자바스크립트에서는 실행 컨텍스트라는 방식으로 되어있다. 

실행 컨텍스트는 세 가지로 나뉜다.

  • Global Execution Context: 함수 호출로 실행되는 코드가 아니라면 코드는 전역 컨텍스트에서 실행된다. 
  • Functional Execution Context: 함수가 호출될 때 해당 함수에 대한 실행 컨텍스트가 생성된다. 각각의 함수들은 자신만의 실행 컨텍스트를 가짐.
  • Eval Function Execution Context: eval 함수는 자신 만의 실행 컨텍스트를 가짐. 

위와 같은 이유는 다음과 같다. 결국 엔진은 하나의 js 파일을 실행하는 것이고 그에 대한 규칙을 만들어 놓았기 때문이다. 하나의 js 파일은 변수와 함수를 선언하는 부분과 함수호출 또는 실행할 수 있는 부분으로 나뉘기 때문이다. 

const a = 1; 
function foo(){
  console.log(a)
}
if(a === 1){
  foo();
}

위와 같은 코드에서 a, foo인 변수명, 함수명을 선언하였고 if문 부분이 선언된 변수, 함수를 이용해서 실행하는 것이 이 파일의 목적인 것이다. 

대략적인 코드 구조를 설명하였고 실행 컨텍스트에 대해서 알아보자.

엔진이 컨텍스트를 만들 때는 다음 두 가지를 항상 만든다. 1. creation phase, 2. execution phase이다. 결국, global 코드에서 한 번 글로벌 실행 컨텍스트를 만들고 그 이후에는 함수나 Eval 실행 컨텍스트를 계속 만들게 되는 것이다.

1. creation phase 

creation phase 에서는 결과적으로 다음과 같은 것을 만들게 된다.

ExecutionContext = {  
  LexicalEnvironment = <ref. to LexicalEnvironment in memory>,  
  VariableEnvironment = <ref. to VariableEnvironment in memory>,
}

다음 코드를 예시로 하겠다.

var a = 20;
var b = 40;
function foo(c = 3) {
  console.log('bar');
}

1.1 Lexical Environment

예시 코드를 기준으로 creation phase가 되면 LexicalEnvironment는 다음과 같은 정보가 들어가게 된다. identifier에 대한 매핑 값과 outer Environment에 대한 참조, this가 무엇인지까지 총 세 가지 종류로 나뉜다.

lexicalEnvironment = {
  1.Environment Record(
    a: 20, 
    b: 40, 
    foo: <ref. to foo function>
  ),
  
  2.Reference to the outer environment,
  3.this binding
}

 

1.1.1. Environment Record

a. Declarative environment record : 가장 먼저 identifier(식별자)인 변수명, 함수명에 대한 정보를 수집한다.

함수가 호출되어 생기는 함수 실행 컨텍스트에서는 식별자로 인자가 있기 때문에 argument 객체 정보도 저장한다. 예를 들면, foo 함수를 호출하는 시점에 실행 컨텍스트가 만들어지게 되는데 이 때는 foo 함수 내부에는 식별자로 쓰이는 인자명인 c가 Environment Record에 저장되게 되는 것이다.

b. Object environment record: 글로벌 실행 컨텍스트에서 추가로 저장되는 것으로 전역 환경에서 제공되는 객체들이 저장된다. 전역 코드가 브라우저에서 실행되는 경우에는 브라우저에서 제공되는 window 객체 같은 것들이 있다. 노드에서 실행되거나 다른 환경에서 실행되면 바뀔 것이다.

1.1.2 Reference to the Outer Environment

감싸고 있는 블락의 Lexical Environment를 참조하는 것으로서 외부의 식별자를 참조할 수 있게 된다.

1.1.3 this binding

전역 실행 컨텍스트는 전역 객체가 this가 된다.

함수 실행 컨텍스트는 함수를 실행한 객체가 this가 된다.

함수에서 this가 무엇인지 파악할 때 호출하는 객체를 알아야 하는데 이와 같은 실행 방식 때문이다.

1.2 Variable Environment

var 키워드로 선언한 변수는 여기에서 저장된다. const, let은 Lexical Environment에서 관리되는 것과 구분하자.

2. execution phase 

코드가 한 줄씩 실행되는 단계이다.

 

실행 컨텍스트 예시

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

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

위 코드를 실행하면서 creation phase, execution phase에서 어떤 작업이 이루어 지는지 살펴보자. 

1. 가장 먼저 creation phase 진행

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>
  }
}

creation phase가 끝나면 위와 같은 GlobalExecutionContext가 만들어지고 let a = 20; 부터 execution을 시작한다.

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

여기 까지 실행되면 a, b 에 대한 값이 평가되어 저장되게 된다.

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>
  }
}

다음 라인으로 c = multiply(20, 30)인데 우측의 multiply(20, 30)부터 호출된다.

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

인자는 이미 호출될 때 값을 전달받아서 인자에 사용된 것을 볼 수 있고 식별자인 g는 아직 실행되지 않았는데 var 키워드이므로 undefined로 되어 있는 것을 볼 수 있다. 호출되는 시점에 다음과 같은 foo에 대한 함수 실행 컨텍스트가 만들어진다. (var g = 20; 라인이 실행되고 나면 g에 값이 할당될 것이다.)

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: undefined
    },
    outer: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}

multiply(20,30)의 결과인  20*30*20이 리턴되고 c에 저장되면 글로벌 컨텍스트는 다음과 같은 상태가 될 것이다. 

GlobalExectionContext = {
LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      a: 20,
      b: 30,
      multiply: < func >
    }
    outer: <null>,
    ThisBinding: <Global Object>
  },
VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      c: 12000,
    }
    outer: <null>,
    ThisBinding: <Global Object>
  }
}

 

 

참조 blog.bitsrc.io/understanding-execution-context-and-execution-stack-in-javascript-1c9ea8642dd0

'웹(web) > 프론트엔드-javascript' 카테고리의 다른 글

크롬 콘솔에서 디버깅하기  (0) 2021.05.05
비동기, Promise  (0) 2020.11.08
scope  (0) 2020.06.29
rest, spread  (0) 2020.06.28
비구조화 할당(구조분해 할당)  (0) 2020.06.28