개발일지

코어 자바스크립트 2강 실행 컨텍스트 본문

코어 자바스크립트 스터디

코어 자바스크립트 2강 실행 컨텍스트

박수미/ 2024. 7. 4. 23:12

2강 실행 컨텍스트

1. 실행 컨텍스트란?

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 자바스크립트의 동적 언어로서의 성격을 잘 파악할 수 있는 개념입니다. 자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅), 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행하는데, 이로 인해 다른 언어에서는 발견할 수 없는 특이한 현상들이 발생합니다.

 

본격적으로 실행 컨텍스트를 살펴보기 앞서 스택과 큐의 개념을 살펴보겠습니다.

출처 코어 자바스크립트

스택- 출입구가 하나뿐인 깊은 우물 같은 데이터 구조입니다. 비어있는 스택에 순서대로 데이터 a, b, c, d를 저장했다면, 꺼낼 때는 반대로 d, c, b, a의 순서로 꺼낼 수밖에 없습니다.

큐- 양쪽이 모두 열려있는 파이프를 떠올리면 됩니다. 종류에 따라 양쪽 모두 입력과 출력이 가능한 큐도 있으나 보통은 한쪽은 입력만, 다른 한쪽은 출력만 담당하는 구조를 말합니다. 

앞서 실행 컨텍스트를 실행할 코드에 제공할 환경 정보들을 모아놓은 객체라고 했습니다. 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장합니다.

 

2. VariableEnvironment

VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다릅니다. 실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용하게 됩니다.

 

3. LexicalEnvironment

LexicalEnvironment에 대한 한국어 번역은 문서마다 제각각 다른데 '어휘적 환경', '정적 환경'이라는 단어가 가장 많이 등장합니다. '어휘적'은 lexical을 영어사전에 대입해서 치환한 것으로 의미가 와닿지 않고, '정적'이라는 말은 수시로 변하는 환경 정보를 의미하는 LexicalEnvironment에 대한 적절한 번역이라고 볼 수 없습니다. 이보다는 '사전적인'이 더욱 어울리는 표현이라 생각합니다.

 

1-3-1 environmentRecord와 호이스팅

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다. 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등이 식별자에 해당합니다. 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집합니다.

변수의 정보를 수집하는 과정을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태입니다. 코드가 실행되기 전임에도 불구하고, 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있게 되는 셈이죠. 그렇다면 엔진의 실제 동작 방식 대신에 '자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다.'라고 생각하더라도 코드를 해석하는 데는 문제가 없을 것입니다. 여기서 호이스팅이라는 개념이 등장합니다.

호이스팅이란 '끌어올리다'라는 의미의 hoist에 ing를 붙여 만든 동명사로, 변수 정보를 수집하는 과정을 더욱 이해하기 쉬운 방법으로 대체한 가상의 개념입니다. 자바스크립트 엔진이 실제로 끌어올리지는 않지만 편의상 끌어올린 것으로 간주하자는 것이죠.

 

호이스팅 규칙

원본코드

function a (x) {  // 수집 대상 1(매개변수)
  console.log(x); // (1)
  var x;          // 수집 대상 2(매개변수)
  console.log(x); // (2)
  var x = 2;      // 수집 대상 3(매개변수)
  console.log(x); // (3)
}
a(1)

출력 예상 (1) 1, (2) undefined, (3) 2

 

매개변수를 변수 선언/할당과 같다고 간주해서 변환한 상태

function a () {
  var x = 1;      // 수집 대상 1(매개변수)
  console.log(x); // (1)
  var x;          // 수집 대상 2(매개변수)
  console.log(x); // (2)
  var x = 2;      // 수집 대상 3(매개변수)
  console.log(x); // (3)
 )
}
a()

 

호이스팅을 마친 상태

function a () {
  var x;          // 수집 대상 1의 변수 선언 부분
  var x;          // 수집 대상 2의 변수 선언 부분
  var x;          // 수집 대상 3의 변수 선언 부분
  
  x = 1;          // 수집 대상 1의 할당 부분
  console.log(x); //(1)
  console.log(x); //(2)
  x = 2;          // 수집 대상 2의 할당 부분
  console.log(x); //(3)
  }
  a(1);

실제 출력 (1) 1, (2) 1, (3) 2

(2)에서 undedined가 아닌 1이 출력된다는 건 호이스팅 개념을 정확히 이해하지 못하면 예측하기 어렵다.

 

함수 선언문과 함수 표현식

둘 모두 함수를 새롭게 정의할 때 쓰이는 방식입니다.

함수 선언문- function 정의부만 존재하 별도의 할당 명령이 없는 것을 의미합니다.

함수 표현식- 정의한 function을 별도의 변수에 할당하는 것을 말합니다.

함수 선언문의 경우 반드시 함수명이 정의돼 있어야 하는 반면, 함수 표현식은 없어도 됩니다.

 

함수를 정의하는 세 가지 방식

function a () {}          // 함수 선언문. 함수명이 곧 변수명
a();   // 실행

var b = function() {}     // (익명) 함수 표현식. 변수명 b가 곧 함수명
b();  // 실행

var c = function d () {}  // 기명 함수 표현식. 변수명은 c, 함수명은 d
c();  // 실행
b();  // 에러

 

선언문과 함수 표현식의 실질적인 차이

원본코드

console.log(sum(1, 2));
console.log(multiply(3, 4));

function sum (a, b) {             // 함수 선언문 sum
  return a + b;
}

var multiply = function (a, b) {  // 함수 표현식 multiply
  return a * b; 
}

 

호이스팅을 마친 상태

var sum = function sum (a, b) { // 함수 선언문은 전체를 호이스팅합니다.
  return a + b;
};
var multiply;                   // 변수는 선언부만 끌어올린다.
console.log(sum(1, 2));
console.log(multiply(3, 4));

multiply = function (a, b) {    // 변수의 할당부는 원래 자리에 남겨둡니다.
  return a * b;
};

함수 선언문은 전체를 호이스팅 한 반면 함수 표현식은 변수 선언부만 호이스팅 했습니다.

함수도 하나의 값으로 취급할 수 있다는 것이 바로 이런 것입니다. 함수를 다른 변수에 값으로써 '할당'한 것이 곧 함수 표현식입니다.

 

2-3-2 스코프, 스코프 체인 outerEnvironmentReference

스코프- 식별자에 대한 유효범위입니다. 어떤 경계 A의 외부에서 선언한 변수는 A의 외부뿐만 아니라 A의 내부에서도 접근이 가능하지만, A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있습니다. 이러한 스코프의 개념은 대부분의 언어에 존재합니다. 자바스크립트도 예외는 아닌데, 다만 ES5까지의 자바스크립트는 특이하게도 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성됩니다.

 

스코프 체인- outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조합니다. '선언하다'라는 행위가 실제로 일어날 수 있는 시점이란 콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 때뿐입니다. 어떤 함수를 선언하는 행위 자체도 하나의 코드에 지나지 않으며, 모든 코드는 실행 컨텍스트가 활성화 상태일 때 실행되기 때문입니다.

 

전역변수와 지역변수 

전역 컨텍스트의 LexicalEnvironemnt에 담긴 변수를 전역변수라 하고, 그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수들은 모두 지역변수입니다. 안전한 코드 구성을 위해 가급적 전역변수의 사용은 최소화하는 것이 좋습니다.

 

this

this에는 실행 컨텍스트를 활성화하는 당시에 지정된 this가 저장됩니다. 함수를 호출 하는 방법에 따라 그 값이 달라지는데. 지정되지 않은 경우에는 전역 객체가 저장됩니다.