JavaScript 프로그램 평가와 실행 과정(2)

Beautify.log·2021년 10월 31일
0
post-thumbnail

저번 포스팅에서는 실행문맥과 렉시컬 환경 컴포넌트, 환경 레코드, 전역환경과 전역객체에 대해 살펴보았습니다.
이번 포스팅에서는 다음과 같은 주제를 살펴봅니다.

  1. 프로그램의 평가와 전역변수
  2. 프로그램 실행과 실행 문맥
  3. 싱글 스레드와 멀티 스레드
  4. 환경 레코드와 지역변수

프로그램의 평가와 전역변수

전역 환경과 전역 객체를 생성한 후에 자바스크립트는 프로그램을 읽어들입니다. 자바스크립트 프로그램을 읽어들인 후에 프로그램을 평가하게 되며 최상위 레벨에 var로 선언한 전역 변수는 전역 환경의 환경 레코드(객체 환경 레코드)의 프로퍼티로 추가됩니다.

GlobalEnvironment = {
  ObjectEnvironmentRecord: {
    bindObject: window
  },
  OuterLexicalEnvironmentReference: null
}

자바스크립트 엔진은 전역 코드를 평가할 때 최상위 스코프에 var 문으로 작성한 전역 변수를 전역 환경의 환경 레코드(객체 환경 레코드)에 property로 기록합니다. 그 property name은 식별자가 이름이 되고 property value는 undefined가 됩니다.
함수의 경우에는 최상위 스코프에 작성된 함수 선언문을 함수 객체로 생성해서 전역 환경의 환경 레코드에 property로 기록합니다.
즉, 최상위 스코프로 선언된 변수나 함수는 프로그램을 평가할 때 객체 환경 레코드에 기록됩니다.

var x = { a: 1, b: 2 };
console.log(window.x);	// Object { a=1, b=2 }

function y(p) {
  return p * p
}
console.log(window.y);	// y(p)

전역변수는 전역객체의 property 또는 전역 객체의 실행 문맥에 들어 있는 환경 레코드(객체 환경 레코드)의 property가 됩니다. 함수 안에 선언된 지역 변수와 중첩 함수 또한 그 함수가 속한 실행 환경의 환경 레코드(선언적 환경 레코드)의 property 입니다.
자바스크립트의 모든 변수를 객체의 property로 간주하면 쉬운 이해가 가능합니다.

최상위 레벨에 선언된 변수와 함수는 프로그램을 평가하는 시점에 객체 환경 레코드에 추가됩니다. 최상위 스코프에서 선언된 함수와 변수가 프로그램을 평가하는 단계에 이미 객체 환경 레코드에 추가 되었기 때문에 코드의 어느 위치에 작성하더라도 전체 프로그램에서 참조할 수 있습니다. 이것은 호이스팅 때문이며 최상위 코드의 변수 선언문과 함수 선언문이 끌어올림 당하는 현상입니다.

또한 var문과 함수 선언문으로 선언한 전역변수는 [[Configuable]] 속성이 false로 설정되어 있으므로 delete 연산자로 삭제하는 것이 불가능합니다.

var x = { a: 1, b: 2 }
console.log(Object.getOwnPropertyDescriptor(window, 'x'));
  // → Object {vaule: {...}, writable: true, enumerable: true, configurable: false}
delete x;
console.log(x);	// → Object {a: 1, b: 2}

var 문을 사용하지 않고 변수를 선언하여 값을 할당하게 되면 프로그램을 실행하는 도중에 ThisBinding이 컴포넌트가 가리키는 객체의 property로 추가됩니다. 전역 객체의 ThisBinding 컴포넌트는 전역 객체를 가리키므로 전역 객체의 property가 됩니다. 또한 [[Configuable]] 속성도 true로 설정되어 delete 연산자로 삭제할 수 있습니다.

x = { a: 1, b: 2 }
console.log(Object.getOwnPropertyDescriptor(window, 'x'));
  // → Object {vaule: {...}, writable: true, enumerable: true, configurable: true}
delete x;
console.log(x);	// → Uncaught ReferenceError: x is not defined

프로그램 실행과 실행 문맥

프로그램이 평가된 다음에 프로그램은 실행되며 이는 실행 문맥(Execution Context) 안에서 실행됩니다.
실행 문맥은 스택이라는 구조로 관리되며 스택은 일종의 자료 구조로 데이터를 아래에서 부터 쌓아 올려 마지막으로 추가한 데이터를 먼저 꺼내는 Last In First Out(후입선출) 방식으로 관리 됩니다.
스택의 가장 윗부분에 데이터를 쌓는 행위를 Push라고 하고, 가장 윗부분에서 데이터를 빼내는 행위를 Pop이라고 합니다.

실행 문맥은 프로그램 실행 중에 스택에 push 되어 실행되는데 가장 먼저 실행되는 코드는 전역 코드이며, 때문에 스택의 맨 아래에는 항상 전역 코드를 실행하기 위한 실행 문맥이 있습니다. 전역 코드 안에서 함수를 실행하면 그 함수를 실행하기 위한 실행 문맥을 스택에 push 합니다.

그리고 그 함수의 작업을 끝내고 함수를 호출한 부분으로 control 할 수 있는 권한이 돌아오면 스택에서 pop하게 됩니다. 이 때 실행하는 함수가 특정 함수의 내부에 중첩된 경우라면 중첩된 함수의 실행 문맥을 새로 만들어 스택에 push 하게 됩니다. 중첩 함수의 실행 문맥은 외부 함수의 실행 문맥 안에서 중첩되지 않습니다.

마찬가지로 함수 스코프 안에 있는 코드를 실행하는 도중에 다른 함수를 호출하면 그 함수의 실행 문맥도 스택에 push 하게 됩니다. 중첩함수를 호출했을 때와 함수를 재귀적으로 호출한 경우에도 똑같이 적용됩니다.

재귀 호출한 함수는 호출한 함수와 같은 함수입니다. 그러나 전혀 다른 함수로 스택에 push 됩니다. 이와 같이 함수의 실행 문맥은 호출될 때마다 스택에 push됩니다. 그리고 return 문이 실행되어 control 할 수 있는 권리가 호출한 코드로 돌아가게 되면 스택에서 pop 됩니다. 때문에 실행 문액 스택을 호출스택(call stack)이라고 합니다.

함수의 실행 문맥은 코드를 호출할 때마다 호출 스택에 push 되고, 종료하면 pop 됩니다.

싱글 스레드와 멀티 스레드

스레드란 프로그램의 처리 흐름을 말합니다. 싱글 스레드와 멀티 스레드 방식이 있는데 각각의 의미를 살펴보면

  • 싱글 스레드 : 프로그램 한 개의 처리 흐름으로 프로그램을 순차적으로 실행함.
  • 멀티 스레드 : 프로그램 여러개의 처리 흐름으로 동시에 여러개를 병렬로 실행함.

자바스크립트는 작업을 싱글 스레드로 처리합니다. 호출 스택에 쌓인 실행 문맥(함수나 코드)을 위에서부터 아래로 차례대로 실행합니다. 실행 문맥 하나의 작업이 끝나면 pop을 하게 되고 아래에 있는 실행 문맥을 실행합니다. 실행 문맥 하나의 작업이 끝날 때까지 또 다른 실행 문맥의 작업을 실행하지 않습니다.

이벤트 처리와 같은 비동기 처리(async ... await ~ 등)도 똑같은 방식으로 실행합니다. 실행할 준비를 마친 상태에서 비동기 처리는 실행하기에 앞서 대기행렬을 만들어줍니다. 그리고 현재 실행 중인 함수의 작업이 끝나면 대기 행렬에서 실행을 기다리리는 첫번째 실행 문맥부터 차례대로 호출 스택에 push해서 실행합니다.

환경 레코드와 지역변수

함수를 호출하게 되면 현재 실행 중인 코드의 작업을 잠깐 멈추고 실행 문맥 영역을 생성합니다.

프로그램 실행 흐름이 그 실행 문맥으로 이동하게 되고 함수의 실행 문맥이 호출 스택에 push 되고 실행 문맥 안에 렉시컬 환경 컴포넌트를 생성합니다. 이 렉시컬 환경 컴포넌트는 환경 레코드를 갖고 있으며 환경 레코드 안에 그 함수 안에 선언된 중첩 함수의 참조와 변수를 읽어들여 기록합니다.

함수 안팎의 환병을 기록하게 되고 이 환경 레코드는 사용자가 읽거나 쓸 수 없으며 다음과 같은 정보를 기록하는 용도로 사용됩니다.

  • 함수의 인자
  • 함수 안에 선언된 중첩 함수의 참조
  • 함수 안에서 var로 선언된 지역 변수
  • arguments

함수가 실행될 때 그 함수의 선언적 환경 레코드에는 이에 대응하는 인수의 값이 설정되고 대응하는 인수가 없으면 undefined가 기본적으로 설정됩니다. 함수 선언문으로 생성한 함수 안의 지역변수에는 함수 선언문으로 생성한 함수 객체의 참조가 설정됩니다. var로 선언한 지역 변수에는 undefined가 설정됩니다. arguments는 인수 값의 전체 목록, length property, callee property를 갖고 있습니다.

함수 실행 문맥, 렉시컬 환경, 환경 레코드가 생성되면 실행 문맥에 있는 ThisBinding 컴포넌트에 그 함수를 호출한 객체의 참조를 저장하게 되고, 이것으로 this의 값을 결정하게 됩니다.
여기에서 this는 동적이며 함수를 호출하는 상황에 따라 가리키는 객체가 바뀝니다.

환경 레코드와 this의 값이 결정되면 함수 안의 코드가 순서대로 실행됩니다. 함수가 실행되는 시점에는 지역 변수 또는 함수 선언문으로 선언한 함수 이름이 함수를 평가하는 시점에서 선언적 레코드에 기록된 상태가 되고, 따라서 변수 또는 함수 선언문이 함수 내부의 어떤 부분에 위치하더라도 함수 전체에서 사용 가능하게 됩니다. 이러한 사실을 근거로 변수 선언문과 함수 선언문이 함수의 첫머리로 호이스팅 된다는 것을 알 수 있습니다.

함수가 종료되어 control할 권리가 호출한 코드로 돌아가게 되면 일반적으로 실행 문맥과 함께 그 안에 있는 렉시컬 환경 컴포넌트에서 사라지게 됩니다. 그러나 그 함수의 바깥에 위치한 함수의 참조가 환경 레코드에 유지되는 경우에는 렉시컬 환경 컴포넌트가 메모리에서 지워지지 않습니다.

출처

이소 히로시, 모던 자바스크립트 입문(길벗), 275-280.

profile
tried ? drinkCoffee : keepGoing;

0개의 댓글