[NodeJS] call stack 원리, execution context, 개념, 과정 예시

Onam Kwon·2023년 4월 27일
0

Node JS

목록 보기
22/25
post-thumbnail

execution context & stack frame

  • 자바스크립트가 작동하는 과정을 공부하다 보면 call stack 이 나오며 stack 자료구조는 모두 알다시피 FILO(First-In-Last-Out)을 따릅니다. call stack 에서 코드의 실행이 이루어지며 FILO 를 통해 함수의 실행 순서를 도중에 잃어버리지 않고 유지하며 작업을 수행할 수 있습니다.
  • 이를 이해하기 위해서는 execution context 와 frame 의 개념을 이해해야 하며 이를 이해하면 디버깅, 최적화 그리고 일반적인 퍼포먼스 위험을 줄이는데 도움을 줍니다. 더 나아가 클로저와 이벤트 루프를 이해하는데 필요한 개념입니다.

EC (execution context)

  • JavaScript 를 실행하면 엔진은 코드를 읽으며 스크립트 파일을 스캔하는데 이때 코드의 실행을 처리하는 EC (execution context, 실행 컨텍스트) 라는 환경을 만듭니다.
  • EC 는 코드가 실행되는 환경의 추상적이고 개념적인 표현 이지만 물리적으로 객체를 가집니다. 이는 아래에서 계속 설명하겠습니다.

EC 의 2가지 단계

  • EC 는 creation phaseexecution phase 두 단계로 이루어 지며 이는 거의 동시에 일어나 정확히 구별되지는 않습니다. 하지만 이를 나누어 이해하면 JS 엔진이 코드를 처리하는 방식을 이해하는데 더 도움을 줍니다.
    • creation phase: 이 단계에서 JS 의 엔진은 EC 를 생성하며, scope chain 초기화, 변수와 함수 선언, 그리고 this 객체를 생성합니다. 변수 선언이 undefined 으로 초기화 되는 과정 또한 이 단계에서 이루어집니다.
    • execution phase: 이 단계에서 JS 의 엔진은 코드를 한줄씩 실행합니다. 스크립트의 문이나 표현을 처리하고 함수를 호출하며 변수에 값을 할당합니다. 위의 creation phase 에서 선언된 변수나 함수는 이 단계에서 사용할 수 있습니다.

EC 의 2가지 종류

  • EC 는 두가지 종류가 있습니다.
    • global execution context: 맨 처음 JavaScript 파일이 실행될 때 생성되며 global scope 의 범위 내부에 정의된 모든 변수와 함수를 포함합니다. 또한 GEC 는 유일하며 최상위에 위치합니다.
    • function execution context: 함수가 호출될 때 마다 매번 생성되며 각 함수의 local scope 는 함수의 로컬 범위를 나타내며 함수 내부에 정의된 모든 변수와 함수를 포함합니다.

EC 생성 조건

  • EC 가 새롭게 생성되는 조건은 아래와 같습니다.
    • EC 는 함수가 호출될때 생성됩니다. 여기서 함수란 user-defined 함수 즉, 일반적으로 개발자가 새롭게 만드는 함수를 의미합니다.
    • 단순히 변수를 선언하거나 연산자를 사용할 때에는 새로운 EC 가 생성되지 않습니다 (기존에 있던 EC 에서 수행됩니다).
    • 몇몇 built-in 함수 (대표적으로 console.log()) 는 새로운 EC 를 생성하지 않습니다. 대신에 기존에 있던 EC 에서 수행됩니다.

EC 구성

  • EC 는 추상적이며 개념적인 표현 방식이지만 실제로 객체로 나타내어 지며 call stack 에 푸쉬 되어 들어갑니다. EC 는 위처럼 구성되어 있으며 GECvariable object (VO)global object (GO)를, FECvariable object (VO)activation object (AO)를 가리킵니다.
  • variable object (VO): 실행 컨텍스트가 생성될 때, JS 엔진은 실행에 사용하는 여러 정보를 담을 객체를 생성하는데 이를 VO 라고 합니다. 현재 컨텍스트에 있는 모든 변수와 함수의 선언을 저장하는 특수 객체 입니다.
  • scope chain: list 형 자료구조로 variable object(VO)activation object (AO) 로 구성되어 있습니다. scope chain 의 첫번째 객체는 GEC 의 경우 GO 를 가리키고, FEC 의 경우 해당 AO 를 가리킵니다. JS 엔진은 첫번째 객체부터 변수 탐색을 시작하며 변수가 발견되거나 전역 범위에 도달할 때 까지 체인을 다음 범위로 이동하며 탐색합니다.
  • this binding: this 값이 할당됩니다. this 값은 함수가 어떻게 호출되는지에 달려 있으며 현재 실행 컨텍스트의 this 값을 정의합니다.

Global Execution Context (GEC) 구성

  • variable object (VO)
    • GEC 의 VO 는 유일하며 global object (GO)를 가리킵니다.
    • VO 는 현재 범위에서 선언된 모든 변수 및 함수를 포함합니다.
    • GO 를 가리키는 이유는 GEC 는 전역범위에 위치하는 EC 이기 때문에 함수의 매개변수가 없기 때문입니다. 따라서 GO 는 전역변수와 전역함수를 포함하며, 매겨변수는 포함하지 않습니다.
      • global object (GO) 는 브라우저의 경우 window 객체를 의미하며 node.js 의 경우 global 객체를 의미합니다.
      • GO 는 스크립트 어디에서든 접근 가능한 다양한 build-in 속성과 메소드를 제공하며 대표적으로 console setTimeout Math 등이 있습니다.
  • scope chain
    • GEC 에서 scope chain 은 한개의 원소만 가지고 있으며 이는 GO 입니다. 따라서 GEC 에서 scope chain 의 첫번째 객체는 GO 를 가리키며 이는 VO가 가리키는 GO 와 일치합니다.
  • this binding

Function Execution Context (FEC) 구성

  • variable object (VO)
    • FEC 에서 VO 는 activation object (AO) 를 가리킵니다.
    • activation object (AO) 는 함수가 호출될 때 마다 생성되며 지역변수 그리고 함수를 포함합니다. 또한 함수가 완전히 종료되면 사라집니다.
    • AO 는 함수 외부에서 접근 할 수 없습니다.
    • argument object: activation object (AO) 의 속성중 하나로 따라서 GEC 에는 없고 FEC 에만 존재합니다. 함수에 전달된 모든 매개변수를 포함하는 배열과 같은 객체입니다. 모든 함수에 대해 자동으로 생성되며, 인수의 인덱스 또는 길이 속성을 사용하여 속성에 액세스할 수 있습니다.
  • scope chain
    • 새로운 함수가 매번 실행 될 때 마다 새로운 AO 가 생성되며 이는 새로운 scope hain 의 맨 앞에 추가되고 그 뒤를 기존에 존재하던 scope chain 이 복사됩니다.
    • 따라서 현재 범위의 scope chain 에서 첫번째 원소가 가리키는 객체는 해당 범위의 VO 가 가리키는 AO 와 항상 동일합니다. 또한 기존의 scope chain 이 뒤따르므로 마지막 객체는 항상 GO 를 가리킵니다.
  • this binding

activation object vs argument object

activation objectargument object
정의함수가 호출될 때 마다 매번 생성되는 특별한 개체로, 함수의 모든 지역변수 선언, 내부 함수 선언, 그리고 this 값을 가지고 있습니다.모든 배개변수를 가지고 있는 list 형 자료구조
접근바로 접근 불가능 하며, 함수의 scope chain 을 통해 접근 가능합니다.arguments 키워드를 통해 바로 접근 가능합니다.
변동성mutable, 지역 변수는 새롭게 추가, 수정 그리고 삭제 될 수 있습니다.immutable, 수정 불가능합니다.

GEC VO vs FEC VO

VO in GECVO in FEC
Points toGlobal Object (GO)현재 함수의 Activation Object (AO)
Contains전역 변수 선언 및 함수 선언현재 함수에 전달된 매개변수와 함수 내에서 선언된 모든 지역 변수 선언
생성 시기전역 범위로 들어갈 때함수가 호출될 때
사용 시기변수와 함수를 찾을 때변수, 함수 그리고 매개변수를 찾을 때
접근 가능 범위코드 어디서나 가능현재 함수 내부에서만 가능

EC 구성의 생성 순서

  • variable object -> scope chain -> this 순으로 생성됩니다.

작동 과정

function myFunction2() {
    console.log('A new FEC has been created by myFunction2.');
    console.log('FEC2 this:', this);
}

function myFunction1() {
    console.log('A new FEC has been created by myFunction1.');
    console.log('FEC1 this:', this, '\n');
    myFunction2.call(myFunction2);
}

console.log('GEC this:', this, '\n');
myFunction1();
  • 위 코드를 실행하면 call stack 에서 이를 처리하는 과정은 아래와 같습니다.

  • 새로운 EC 가 생성될 때 마다 call stacktop 에 추가되며 회색으로 칠한 부분은 control (제어권) 을 가지고 있음을 나타냅니다.
  • 맨 처음 GEC 가 추가되며 myFunction1myFunction2 가 추가될 때 마다 새로운 execution context 가 call stack 의 탑에 추가됩니다.
    • 호출된 역순으로 함수가 완료되며 call stack 에서 또한 역순으로 사라집니다.
  • 이를 위에서 소개한 EC 구성을 포함해 설명하면 그 과정은 아래 그림과 같습니다.

  • 아래는 온라인 JS 인터프리터로 위 코드를 실행하면 나오는 결과입니다.
  • this 바인딩이 가리키는 객체는 인터프리터나 환경에 따라 다르지만 위의 경우 다음과 같습니다.
    • GEC에서 this 바인딩이 가리키는 객체는 아무것도 가리키지 않습니다.
      • 저는 온라인 환경에서 실행하였고 이경우 this 바인딩이 GEC에서 아무것도 제공되지 않는것 처럼 보입니다.
    • FEC1에서 this 바인딩이 가리키는 객체는 global object 입니다.
    • FEC2에서 this 바인딩이 가리키는 객체는 해당 EC의 activation object입니다.
      • myFunction2 를 호출할 때 call 메소드를 사용하면 인자로 주어지는 객체를 가리키게 됩니다. 따라서 FEC2는 myFunction2 를 가리킵니다.
GEC this: {} 

A new FEC has been created by myFunction1.
FEC1 this: Object [global] {
  global: [Circular],
  clearInterval: [Function: clearInterval],
  clearTimeout: [Function: clearTimeout],
  setInterval: [Function: setInterval],
  setTimeout: [Function: setTimeout] {
    [Symbol(nodejs.util.promisify.custom)]: [Function]
  },
  queueMicrotask: [Function: queueMicrotask],
  clearImmediate: [Function: clearImmediate],
  setImmediate: [Function: setImmediate] {
    [Symbol(nodejs.util.promisify.custom)]: [Function]
  }
} 

A new FEC has been created by myFunction2.
FEC2 this: [Function: myFunction2]
  • 위의 코드를 예제로 들었을 경우, call stack 에는 총 3개의 EC 가 push 되며, 제일 먼저 GEC 그 후 두개의FEC 가 들어옵니다.
  • 첫번째는 GEC 전역 실행 컨텍스트 입니다.
    • variable object(VO)global object(GO) 를 가리킵니다.
    • scope chain 의 원소 갯수는 하나이며 global object(GO) 를 가리킵니다.
    • this bindingglobal object(GO) 를 가리킵니다.
  • 두번째는 FEC 함수 실행 컨텍스트 입니다. myFunction1 함수의 호출로 인해 생성됩니다.
    • variable object(VO) 는 해당 함수의 activation object(AO) 를 가리킵니다.
    • scope chain 의 전체 원소 갯수는 2개이며 첫번째 원소는 해당 함수의 AO를 가리키고 두번째 원소는 GO를 가리킵니다.
    • this binding은 FEC1에서 명시적으로 바인딩 되지 않았기 때문에 global object(GO)를 가리킵니다.
  • 세번째는 FEC 함수 실행 컨텍스트 입니다. myFunction2 함수의 호출로 인해 생성됩니다.
    • variable object(VO) 는 해당 함수의 activation object(AO) 를 가리킵니다.
    • scope chain 의 전체 원소 갯수는 3개이며 첫번째 원소는 해당 함수의 AO를 가리키고 두번째 원소는 FEC1의 AO를 가리킵니다. 마지막으로 3번째 원소는 GO를 가리킵니다.
    • this binding은 FEC1에서 myFunction2를 호출할 때 call 메소드를 사용해 myFunction2 를 인자로 넘겨주었기 때문에 FEC2의 AO를 가리킵니다.

주의할 점

  • GO이든 AO이든 주의할 점은 두가지 모두 변수 선언 과 함수 선언에 관한 정보를 가지고 있는것입니다.
  • 변수의 초기화 값과 함수의 구현부는 이곳에 저장되지 않습니다.
  • AO의 argument object 는 매개변수를 가지고 있는것이지 변수의 초기화 값을 포함한다는 의미가 아닙니다.

References

profile
권오남 / Onam Kwon

0개의 댓글