ARTICLE | 실행 컨텍스트(ES5 이후 버전에 맞게 수정할 예정)

noopy·2021년 8월 18일
1

📃 ARTICLE

목록 보기
3/5

렉시컬 환경, 클로저... 이것들은 다 뭐랑 관련된걸까?

그렇다면 코어 자바스크립트 선생님께 실행 컨텍스트를 여쭤보자구요~!
(광고 아닙니다.. 책이 진짜 좋아서 그런고임....)

🧐 실행 컨텍스트

JS 엔진이 코드를 실행하기 위해 필요한 정보를 수집하는데,
이러한 정보들을 모아놓은 객체를 실행 컨텍스트라 한다.
실행 컨텍스트는 동일한 환경(스코프)에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아
하나의 컨텍스트를 구성하고, 이를 콜스택 안에 쌓아올렸다가 실행해서 pop하는 식이다.

컨텍스트는 어떻게 생성될까?

실행 컨텍스트

  • 전역 코드 (전역 영역에 존재하는 코드)
  • eval 코드 (eval 함수로 실행되는 코드)
  • 함수 코드 (함수 내에 존재하는 코드) ⭐️

에 의해 생성되어 콜스택에 쌓이는데,
전역 코드는 JS 파일이 실행되면 자동으로 생성되므로 넘어가고,
보통은 함수가 실행될 때 하나의 컨텍스트가 생성되는 것으로 기억하는 것이 좋겠다.

eval은 왜 따로 컨텍스트가 생성될까?

예시를 통해 알아보자.

// (1) 전역 컨텍스트
let a = 1;
function outer() {
  function inner() {
    console.log(a);
    let a = 3;
  }
  inner(); // (3) inner 함수 실행
  console.log(a);
}
outer(); // (2) outer 함수 실행
console.log(a);
  1. JS 파일을 실행하면 전역 컨텍스트가 콜스택에 담긴다.

    현재 콜스택에는 전역 컨텍스트밖에 없으므로
    전역 컨텍스트와 관련된 코드들을 순차적으로 진행한다.

  2. outer()을 만나면 JS 엔진이 outer 함수에 대한 환경 정보를 수집해
    outer 실행 컨텍스트를 생성하고 콜스택에 담는다.

    콜스택 맨 위부터 우선 실행하므로 전역 컨텍스트 관련 코드 실행을 일시중단 후,
    outer 컨텍스트 => outer 함수 내부 코드들을 순차적으로 실행한다.

  3. inner()를 만나면 inner 함수 실행 컨텍스트가 콜스택 맨 상단에 쌓이고
    outer 컨텍스트 관련 코드들을 실행 중단 후 inner 함수 내부 코드를 순서대로 진행한다.

  4. inner 함수 내부 코드들이 다 실행되고 나면
    inner 실행 콘텍스트가 콜스택에서 제거된다.

    콜스택 맨 상단에 outer 컨텍스트가 있으므로,
    일시중단했던 (3)의 다음 줄부터 이어서 실행한다.

  5. outer 함수 내부의 코드들이 다 실행되고 나면
    outer 컨텍스트가 콜스택에서 제거되고 전역 컨텍스트만 남는다.

  6. console.log(a)가 실행되고나면
    전역 공간에 실행할 코드가 없기 때문에 전역 컨텍스트가 제거되고
    콜스택이 빈 채로 종료된다.

🧐 환경 정보는 무엇일까?

실행 컨텍스트는 동일한 환경(스코프)에 있는 코드들을 실행할 때
필요한 환경 정보들을 모아 하나의 컨텍스트를 구성한다.

환경 정보는 실행 컨텍스트가 생성될 때 수집된 정보로,

  • variableEnvironment
  • LexicalEnvironment
    ThisBinding (ES5 이후부터는 Lexical Environment에서 this binding 관리)

으로 구성된다.

🍡variableEnvironment

실행 컨텍스트 생성 시 variableEnvironment에 정보를 먼저 담고,
복사 해 LexicalEnvironment를 만든다.
이후부턴 LexicalEnvironment를 주로 활용한다.

초기화 과정 중엔 둘이 완전히 동일하기 때문에 구체적인 것은 LexicalEnvironment에서 살펴보자.

🍡LexicalEnvironment


variableEnvironmentLexicalEnvironment 내부는

  • enviromentRecord (식별자 정보)
  • outer-LexicalEnvironment (외부환경 정보)
    로 구성되어 있다.

environmentRecord

enviromentRecord는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 이 객체에 프로퍼티로 저장된다.

예를 들어 함수가 실행되며 컨텍스트가 구성될 텐데, 이 실행 함수에 지정된
매개변수 식별자,
함수 선언식으로 선언된 함수 자체 (함수 표현식 제외),
var로 선언된 변수의 식별자 등이 저장된다.

호이스팅

무언가 떠오르지 않는가?
함수 선언식으로 선언된 함수와, var로 선언된 변수는
호이스팅에 의해 코드 상단부로 끌어올려진다는 것...!

호이스팅이 바로 실행 컨텍스트의
Lexical(variable)EnvironmentenviromentRecord에 의해 일어나는 일이다 🙀.
실제로는 끌어올려지는 것이 아닌 enviromentRecord
식별자 정보를 수집하며 환경 정보를 구성하는 것으로,
코드가 실행되기 전에 환경 정보가 수집되어
JS 엔진이 해당 Environment에 속한 변수명들을 알고 있는 셈이다.

function a () {
  console.log(b); // (1)
  var b = 'bbb';
  console.log(b); // (2)
  function b() {}
  console.log(b); // (3)
}
a();

😎 과정 설명
1. a함수가 실행되며 a함수의 실행 컨텍스트가 생성된다.
2. 이 때 enviromentRecord에 식별자 정보가 수집된다.

⚠️ 식별자 정보를 수집할 땐 변수 선언부(할당부 X)와 함수 선언식의 정보를 수집한다.
변수는 선언부만 수집하지만, 함수 선언은 함수 전체를 수집한다.
수집하는 과정을 아래와 같이 표현했으나, 실제로 저렇게 변하는 것은 아니다.


3. var 변수의 선언부함수 선언식으로 선언된 함수의 정보가 수집되었다.
이때 함수 선언식 함수는 호이스팅 시,
함수명으로 선언된 변수에 함수가 할당된 것으로 생각할 수 있다.

4. var b 변수에 함수 b가 할당됐기 때문에 (1)엔 b함수가 출력될 것이다.
b에 'bbb'가 할당된 직후 (2), (3)엔 bbb가 출력된다.

5. 함수 내부에 모든 코드가 실행되어서 a 함수의 실행 컨텍스트가 콜스택에서 제거된다.

함수 선언식 vs 함수 표현식

함수 선언식: function b () {}; 형태
함수 표현식: 변수 b = function () {}; 형태 👉🏻 (익명) 함수 표현식

함수 선언식은 호이스팅 과정에서 environmentRecord에 함수가 통채로 수집된다.
반면 함수 표현식은 변수에 값으로써 함수가 할당된 형태로,
변수 선언부만 수집되고 나머지 할당부는 수집되지 않는다.
결과적으로, 함수 표현식을 선언 전에 호출하면
선언부만 수집되었기 때문에 호출이 불가하다고 나올 것이다.

outer-EnvironmentReference

스코프: 식별자에 대한 유효범위.
스코프체인: 스코프를 안에서부터 바깥으로 차례로 검색해나가는 것.
outer-EnvironmentReference에 저장된
상위 스코프의LexicalEnvironment를 탐색하면서 스코프체인이 가능도록 한다.
때문에 outer-EnvironmentReference연결리스트로 되어있다.

outer-EnvironmentReference는 현재 호출된 함수가 선언될 당시LexicalEnvironment를 참조한다.

말이 어렵기 때문에, 실제 코드를 보며 해석해보자.

⚠️ 그림 속 약자는 아래의 축약 의미이다.
L.E = LexicalEnvironment
e = environmentRecord
o = 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. JS 파일이 실행되며 자동으로 전역 컨텍스트가 콜스택에 생성된다(활성화).

    전역 컨텍스트의 environmentRecord(e){a, outer} 식별자가 저장된다.
    전역 컨텍스트는 선언 시점이 없으므로
    전역 컨텍스트의 outerEnvironmentReference(o)엔 아무것도 안 담긴다.

  2. JS엔진이 환경 정보를 수집한 후 위에서 아래로 코드를 실행한다.
    전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당한다.

  3. outer()을 실행한다. 함수가 실행되면 컨텍스트가 콜스택에 생성(활성화)되고,
    환경 정보를 수집하기 위해 outer 함수 내부 코드를 살핀다.
    outer 컨텍스트의 environmentRecord(e){inner} 식별자가 저장된다.

outerEnvironmentReference(o)
outer 함수가 선언될 당시의 LexicalEnvironment가 담긴다.
outer 함수는 전역 공간에서 선언됐으므로
전역 컨텍스트의 LexicalEnvironment를 참조복사한다.

4. outer 스코프에 있는 inner 변수에 함수를 할당한다.

5. inner 함수를 호출한다.
마찬가지로 outer 함수의 코드 실행이 일시 중단되고,
콜스택에 inner 실행 컨텍스트가 활성화된다.

inner 컨텍스트environmentRecord(e){a} 식별자가 저장된다.
outerEnvironmentReference(o)inner 함수가 선언될 당시의
LexicalEnvironment가 담긴다.
inner 함수는 outer 함수 내부에서 선언됐으므로
outer 함수의 LexicalEnvironment를 참조복사한다.

6. console.log(a)에서 a에 접근하려 하는데,
inner 컨텍스트environmentRecord(e)에 a를 찾는다.
선언부는 있으나 값은 없으므로 undefined 출력.
inner 스코프에 있는 변수 a에 3을 할당한다.

7. inner 함수 실행이 끝났으므로 콜스택에서 inner 컨텍스트가 제거되고,
outer 실행 컨텍스트가 활성화되며 일시중단됐던 다음 코드부터 실행한다.

8. console.log(a)에서 a에 접근하고자
outer 컨텍스트의 environmentRecord(e)에서 a를 찾는다.
a 식별자가 없으므로 전 전역 EnvironmentReference(o)의 L.E에서 찾는다.
a가 있으니 a에 저장된 값 1을 반환한다.

9. outer 함수 실행이 종료됐으므로, 콜스택에서 삭제되고
전역 컨텍스트가 활성화되며 일시중단됐던 다음코드, console.log(a)를 실행한다.

전역 컨텍스트의 environmentRecord(e)에서 a를 찾고, 있으므로 1을 반환한다.

10. 모든 함수 실행이 종료됐으므로 콜스택이 비워진 채로 끝이난다.

🍡ThisBinding

실행 컨텍스트의 thisBinding엔 this로 지정된 객체가 저장된다.
실행 컨텍스트가 활성화(함수 호출) 당시에 this가 지정되지 않았을 경우,
this는 바로 전역 객체가 된다.

가깝지만 먼 당신... this에서 자세히 정리했다!!


이제 테스형도 실행 컨텍스트를 마스터 하였다. 따봉 👍

느낀점

와 오늘도 실행 컨텍스트에게 정복당할 뻔했다. 생각보다 만만치 않았다...
코드로만 읽으려니 머릿속에 추상적으로 남아있어 그림으로 하나씩 정리를 했는데, 역시 쏙쏙 들어오는 것이 감칠맛이 난다. 하지만 시간은 두배가 돼...

참고 사이트)
실행 컨텍스트와 자바스크립트의 동작 원리
코어 자바스크립트
ES5 이후 실행컨텍스트

profile
💪🏻 아는 걸 설명할 줄 아는 개발자 되기

0개의 댓글