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

devmin24·2021년 8월 19일
1

실행 컨텍스트란?

실행할 코드에 제공할 환경 정보들을 모아놓은 객체.

자바스크립트는,

  1. 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅)
  2. 외부 환경 정보를 구성하고
  3. this 값을 설정하는 등의 동작을 수행하는데, 이로 인해 다른 언어에서는 발견할 수 없는 특이한 현상들이 발생한다.

스택과 큐

스택(stack) : 출입구가 하나뿐인 마치 항아리같은 데이터 구조이다. 마지막에 쌓인 데이터를 가장 먼저 꺼낼 수 있다.
큐(queue) : 출입구가 양쪽으로 모두 열려있는 마치 파이프같은 데이터 구조이다. 데이터가 쌓인 순서대로 처리한다.

동일한 환경에 있는 코드들을 실행할 때,

  1. 필요한 환경 정보들을 모아 컨텍스트를 구성하고
  2. 이를 콜 스택에 쌓아올렸다가
  3. 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.

하나의 실행 컨텍스트를 구성할 수 있는 방법으로, 전역공간, eval() 함수, 함수 실행, 블록(ES6+) 등이 있다.

처음 자바스크립트 코드를 실행하면,

  1. 전역 컨텍스트가 콜 스택에 담긴다.(최상단의 공간은 코드 내부에서 별도의 실행 명령이 없어도 브라우저에서 자동으로 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 이해해도 된다.)
  2. 전역 컨텍스트와 관련된 코드들을 순차로 진행하다가 A 함수를 호출하면 그 함수에 대한 환경 정보를 수집해서 실행 컨텍스트를 생성한 후 콜 스택에 담는다.
  3. 콜 스택 맨 위에는 A 함수 실행 컨텍스트가 놓인 상태가 되었다. 전역 컨텍스트와 관련된 코드의 실행을 일시중단하고 대신 A 함수 실행 컨텍스트와 관련된 코드를 순차로 실행한다.
  4. A 함수의 실행이 종료되면 A 함수의 실행 컨텍스트가 콜 스택에서 제거되고, 일시중단했던 전역 컨텍스트의 코드들을 이어서 실행한다.
  5. 전역 컨텍스트도 실행을 마치면 콜 스택에서 제거되고 콜 스택은 아무것도 남지 않은 상태로 종료된다.

실행 컨텍스트의 수집 정보

VariableEnvironment : 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷으로 변경 사항은 반영되지 않음.

  • environmentRecord (snapshot)
  • outerEnvironmentReference (snapshot)

LexicalEnvironment : 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영된다.

  • environmentRecord
  • outerEnvironmentReference

ThisBinding : this 식별자가 바라봐야 할 대상 객체.


environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보(매개변수 이름, 함수 선언, 변수명 등)들이 저장된다. 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다.

전역 실행 컨텍스트는 변수 객체를 생성하는 대신 자바스크립트 구동 환경이 별도로 제공하는 객체인 전역 객체를 활용한다.

위의 말대로면, 코드가 실행되기 전인데도 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명들을 모두 알게 된다. '자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다'라고 생각해도 코드를 해석하는 데는 문제될 것이 전혀 없을 것이다. 여기서 호이스팅이란 개념을 떠올릴 수 있다.

호이스팅

호이스팅은 변수 정보를 수집하는 과정을 더욱 이해하기 쉬운 방법으로 대체한 가상의 개념이다.

호이스팅을 이해하기 쉽게 코드로 표현해 볼 것이다. 이 코드는 실제 엔진이 이러한 변환 과정을 거친다는 것이 아닌, 이해하기 쉽게 표현하기 위함이다.

function a () {
  console.log(b);
  
  var b = 'bbb';
  console.log(b);
  
  function b () { }
  console.log(b);
}
a();

위의 코드를 그대로만 해석한다면 console에는 1. 에러 or undefined 2. 'bbb' 3. b 함수가 출력될 것으로 예상될 것이다. 하지만 틀렸다. 호이스팅 되기 때문이다.

  • a 함수를 실행하는 순간 a 함수의 실행 컨텍스트가 생성된다. 이 때 변수명과 함수 선언의 정보를 위로 끌어올린다.(호이스팅)
  • 변수는 선언부와 할당부를 나누어 선언부만 끌어올리고, 함수 선언은 함수 전체를 끌어올린다.

설명대로의 코드를 다시 표현해보겠다.

function a () {
  var b; // 수집 대상 1. 변수는 선언부만 끌어올린다.
  function b () { } // 수집 대상 2. 함수 선언은 전체를 끌어올린다.
  
  console.log(b);
  b = 'bbb';
  console.log(b);
  console.log(b);
}
a();

여기서, 호이스팅이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있다.
아래의 코드로 쉽게 이해해보자.

function a () {
  var b; 
  var b = function b () { } // 함수명으로 선언한 변수에 함수를 할당.
  
  console.log(b);
  b = 'bbb';
  console.log(b);
  console.log(b);
}
a();

위에서 예상한 답은 1. 에러 or undefined 2. 'bbb' 3. b 함수 였지만, 실제로는 1. b 함수 2. 'bbb' 3. 'bbb'가 실행되는 것을 볼 수 있다.

함수 선언문과 함수 표현식

함수 선언문
1. function 정의부만 존재하고 별도의 할당 명령 없음
2. 반드시 함수명이 정의되야함.
3. 전체를 호이스팅한다.

함수 표현식
1. 정의한 function을 별도의 변수에 할당한다.
2. 함수명이 있어도, 없어도 된다.
(함수명 정의한 것 : 기명 함수 표현식, 정의하지 않은 것 : 익명 함수 표현식)
3. 변수는 선언부만 끌어올리고, 할당부는 원래 자리에 남겨진다.
4. 기명 함수 표현식은 외부에서는 함수명으로 호출할 수 없고, 함수명은 오직 함수 내부에서만 접근할 수 있다.

// 함수를 정의하는 세 가지 방식
function a () { ... } // 함수 선언문. 함수명 a가 곧 변수명.
a(); // 실행 ok.

var b = function () { ... } // 익명 함수 표현식. 변수명 b가 곧 함수명.
b(); // 실행 ok.
                     
var c = function d() { ... }
c(); // 실행 ok.
d(); // 에러!

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보(매개변수 이름, 함수 선언, 변수명 등)들이 저장된다.는 설명을 다시 한번 되새겨보자. 호이스팅 시 변수와 함수 선언을 상단으로 끌어온다. 그 말은 즉, 함수 선언문은 전체를 호이스팅하고 함수 표현식은 변수 선언부만 호이스팅한다.

함수 선언문으로 코드를 작성하면 선언 전에도 호출하여 실행할 수 있지만, 함수 표현식은 선언된 이후에 호출하여 실행할 수 있다.

동일한 변수명에 서로 다른 값을 할당하는 경우 나중에 할당한 값이 먼저 할당한 값을 덮어씌운다.
만약, 동일한 변수명에 함수 선언문으로 코드를 작성 시 나중에 할당한 함수가 덮어씌워져 실행되므로 에러를 발생시킬 수 있다.

Takeaway

오늘은 실행 컨텍스트와 호이스팅에 대해 공부해보았다. 쉽기도 했지만 헷갈리는 부분이 많았던 것 같다. 하지만 개념을 제대로 알고나니 코드를 보는 시야가 넓어졌다는 느낌이 들었다.
특히, 함수 선언문과 함수 표현식에서 강한 충격을 받았다. 이 개념을 모른 채로 현업에 들어가 내 맘대로 코드를 작성한다면?.. 끔찍한 에러들을 보았을 것이다.
그런 실수를 경험하기 전에 개념들을 익혀둘 수 있어서 정말 다행이라고 생각했다!

profile
꾸준함, 열정 한 가득 챙겨 끝없는 목표를 향해 달려가는 개발자👩‍💻

0개의 댓글