[코어 자바스크립트] 2. 실행 컨텍스트

홍예찬·2021년 1월 7일
0
post-thumbnail

2-1 실행 컨텍스트란?

실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 자바 스크립트의 동적 언어로서의 성격을 가장 잘 파악할 수 있는 개념입니다. 자바스크립트는 실행 컨텍스트 객체가 활성화되는 시점에 선언된 변수를 호이스팅하고, 외부 환경 정보를 구성하고, this값을 설정하는 등의
동작을 수행하게 됩니다.

① Stack

  • 출입구가 하나뿐인 우물 같은 데이터 구조.
  • 비어있는 스택에 a,b,c,d의 순서대로 저장했다면 꺼낼 때는 d,c,b,a 인 순서로 꺼낼 수밖에 없음(First In Last Out).
  • 많은 프로그래밍 언어들이 이와 같이 스택이 넘칠 때 에러를 나타냄.

② Queue

  • 양쪽이 모두 열려있는 파이프 같은 데이터 구조.
  • 보통 한 쪽은 입력을, 다른 한쪽은 출력만을 담당하는 구조.
  • 데이터가 a,b,c,d의 순서로 저장됐다면 꺼낼 때 역시 a,b,c,d의 순서로 꺼내짐(First In First Out).

동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아올렸다가 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 방식으로 전체 코드의 환경과 순서를 보장합니다. 우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것입니다.

보다 나은 이해를 위해 코드로 설명하겠습니다.

// --------------------------(1)
var a = 1;
function outer() {
  function inner() {
  	console.log(a) // undefined
    var a = 3;
  }
  inner(); // ---------------(2)
  console.log(a) //1
}
outer(); // -----------------(3)
console.log(a);  //1

위의 코드는 다음과 같은 순서로 실행되게 됩니다.


  1. 전역 컨텍스트가 콜 스택에 담김
  2. outer 함수 호출 시(3) 자바스크립트 엔진은 outer에 대한 환경 정보를 수집, outer 실행 컨텍스트를 콜 스택에 담음.
  3. 콜 스택 최상단에 outer 실행 컨텍스트가 담겼기 때문에 전역 컨텍스트와 관련된 코드 실행 일시중단, outer 실행 컨텍스트와 관련된 코드(outer 내부 코드) 순차적 실행
  4. inner 함수 호출(2)시 inner에 대한 환경 정보를 수집, inner 실행 컨텍스트를 콜 스택에 담음
  5. outer 컨텍스트와 관련된 코드의 실행 일시중단, inner 실행 컨텍스트와 관련된 코드 순차적 실행

이렇게 실행 컨텍스트가 활성화되면 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드를 실행하는데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장합니다.이 객체에 담기는 정보들은 다음과 같습니다.


VariableEnvironment : 현재 컨텍스트 내의 식별자들에 대한 정보 & 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경 사항은 반영되지 않음
LexicalEnvironment : 처음에는 VariableEnvrionment와 동일하나 변경 사항이 실시간으로 반영
ThisBinding : this 식별자가 바라봐야 할 대상 객체


2-2 Variable Environment

VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같으나, 최초 실행 시의 스냅샷을 유지한다는 점에서 다릅니다. 실행 컨텍스트를 생성할 때 VariableEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용하게 됩니다. 내부는 동일하게 environmentRecord와 outer-EnvironmentReference로 구성되어 있습니다.

2-3 Lexical Environment

① environmentRecord

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다. 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자등이 식별자에 해당됩니다. 컨텍스트 내부를 처움부터 끝까지 순서대로 수집하게 됩니다.
environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지에만 관심이 있고 각 식별자에 어떤 값이 할당될 것인지는 관심이 없습니다. 매개변수의 경우에도 마찬가지입니다.

② Hoisting

호이스팅이란, 자바스크립트 엔진이 변수 정보를 수집하는 과정을 이해하기 쉬운 방법으로 대체한 가상의 개념입니다. 실제로 끌어올리진 않지만 편의상 끌어올린 것으로 간주하는 것입니다. 이 역시도 코드를 통해 살펴보겠습니다.


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)

위의 코드에 hoisting 개념을 더해보면 이렇게 변환할 수 있겠습니다(그러나 자바스크립트 엔진이 실제로 변경하는 것은 아닙니다! 이해를 위한 예시일 뿐입니다.)


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

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

호이스팅을 마친 상태

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

따라서 결과값은 1, 1, 2 로 나오게 됩니다.

③ 함수 선언문과 함수 표현식

두 표현 모두 함수를 새롭게 정의할 때 쓰이는 방식인데, 그 중 함수 선언문함수의 정의부만 존재하고 별도의 할당 명령이 없는 것을 의미하고, 함수 표현식정의한 함수를 별도의 변수에 할당하는 것을 의미합니다. 함수 선언문과 함수 표현식의 가장 큰 차이는 Hoisting에 있습니다. 이 역시도 코드를 통해 살펴보죠.


원본 코드

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) {	//함수 선언문은 함수 전체를 Hoisting합니다.
  return a + b;
}
var multiply;	//변수는 선언부만 끌어올립니다. 

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

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

multiply에는 값이 할당돼있지 않기 때문에 multiply is not a function이라는 에러 메세지가 출력되고 변수의 할당부는 실행되지 않은 채 런타임이 종료됩니다.

이런 이유로 대규모 프로젝트에서 함수를 사용하거나, 협업을 진행하게 될 때, 비교적 안전한 함수 표현식을 사용하는 것이 안전합니다. 전역 컨텍스트가 활성화될 때 전역공간에 선언된 함수들이 모두 가장 위로 끌어올려지기 때문에 만약 동일한 변수명에 서로 다른 값을 할당할 경우 에러가 나타나기 때문입니다.

④ 스코프, 스코프 체인

스코프는 식별자에 대한 유효범위를 뜻합니다. ES5까지의 자바스크립트는 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성됩니다.(ES6에서 블록 스코프가 새롭게 추가되었습니다. let, const, class, arrow function등이 있습니다.)
식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인이라고 합니다. 그리고 이를 가능하게 하는 것이 outerEnvironmentReference 입니다.
이 역시도 코드를 통해 살펴보겠습니다.


var a = 1;
var outer = function () {
  var inner = function () {
      console.log(a);
      var a = 3;
  }
  inner();
  console.log(a)
}
outer();
console.log(a)
  1. 전역 컨텍스트가 활성화됩니다. 전역 컨텍스트의 envrionmentRecord에 { a, outer }식별자를 저장합니다. 전역 컨텍스트는 선언 시점이 없기 때문에 outerEnvironmentReference에는 아무것도 담기지 않습니다.(this:전역 객체)
  2. 전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당합니다.
  3. outer 함수를 호출합니다. 이로 인해 전역 컨텍스트는 잠시 중단되고 outer 실행 컨텍스트가 활성화되어 2번째 줄로 이동합니다.
  4. outer 실행 컨텍스트의 envrionmentRecord에 { inner } 식별자를 지정합니다. outerEnvironmentReference에는 outer 함수가 선언될 당시의 LexicalEnvironment가 담기게 됩니다. outer 함수의 경우 전역 공간에서 선언됐으므로 전역 컨텍스트의 LexicalEnvironment를 참조복사합니다. 이를 [GLOBAL, { a, outer }]라고 표기합니다. 첫 번째는 실행 컨텍스트의 이름, 두 번째는 environmentRecord 객체입니다.(this: 전역 객체)
  5. outer 스코프에 있는 변수 inner에 함수를 할당합니다.
  6. inner 함수를 호출합니다. 이로 인해 outer 실행 컨텍스트는 잠시 중단되고 inner 실행 컨텍스트가 활성화 됩니다.
  7. inner 실행 컨텍스트의 envrionmentRecord에 { a } 식별자를 지정합니다. outerEnvironmentReference에는 inner 함수가 선언될 당시의 LexicalEnvironment가 담기게 됩니다. inner 함수의 경우 outer함수 내부에서 선언됐으므로 outer 함수의 LexicalEnvironment인 [ outer, { inner }]를 참조복사합니다.(this: 전역 객체)
  8. 식별자 a에 접근합니다. 현재 활성화 상태인 inner 컨텍스트의 environmentRecord에서 a를 검색합니다. a를 발견했지만 아직 할당된 값이 없어 undefined를 출력하게 됩니다.
  9. inner 스코프에 있는 변수 a에 3을 할당합니다.
  10. inner함수 실행이 종료되면서 inner 실행 컨텍스트가 콜 스택에서 제거되고 outer 컨텍스트가 다시 실행됩니다.
  11. 식별자 a에 접근합니다. 이 때 자바스크립트 엔진은 활성화된 실행 컨텍스트의 LexicalEnvironment에 접근합니다. 첫 요소의 environmentRecord에 a가 있는지 찾아보고, 없으면 outerEnvrionmentReference에 있는 envrionmentRecord에 넘어가는 식으로 계속 검색합니다. 전역 LexicalEnvrionment에 a가 있으므로 할당된 값 1을 출력합니다.
  12. outer 함수 실행이 종료되면서 outer 실행 컨텍스트가 콜 스택에서 제거되고 전역 컨텍스트가 다시 실행됩니다.
  13. 식별자 a에 접근합니다. 현재 활성화 상태인 전역 컨텍스트의 environmentRecord에서 a를 검색합니다. 1을 출력하게 되고 이로써 모든 코드의 실행이 완료됩니다. 전역 컨텍스트가 콜 스택에서 제거되고 종료됩니다.

이처럼 함수 내부에서 특정 변수에 접근하고자 할 때, 먼저 environmentRecord부터 검색하고 존재하지 않을 경우 outerEnvironmentReference로 넘어가게 됩니다. 이를 변수 은닉화라고 합니다.

위의 글은 온전히 학습 목적을 위해 작성한 글입니다. 위 내용의 모든 지적 재산권,저작권은 코어 자바스크립트 저자에게 있으며, 무단 전재 및 재배포를 금합니다.

profile
내실 있는 프론트엔드 개발자가 되기 위해 오늘도 최선을 다하고 있습니다.

0개의 댓글