사실 다른 블로그에서 정리가 잘 되어 있어서, 안 하려고 했는데 다음 글을 위해 정리 좀 하려고 한다. 일단 개념들은 자바스크립트 Deep Dive를 기반으로 정리를 할 것이다. 하나하나가 내용이 좀 많기 때문에 적당히 흐름대로 정리하려고 한다. 그럼 시작하자
먼저 자바스크립트 실행 컨텍스트 부터 시작해보자, 아 그전에 간단하게 자바스크립트 런타임에 대해 이야기 하겠다. 런타임이란 해당 프로그래밍 언어로 작성된 코드가 구동되는 환경인데 , 자바스크립트에서는 크게 자바스크립트 엔진과 웹 api로 이뤄져 있다 보면 된다. 엔진은 크게 메모리 힙과 콜 스택으로 이뤄져 있다. 런타임으로는 우리가 아는 브라우저랑 Node가 있을 것이다. v8 엔진을 쓴다고 한다.
자바스크립트는 단일 쓰레드로 동작한다고 하는데, 그렇게 단순하게 돌아가는 건 아니고, 자바스크립트 코드를 읽고 실행하는 것을 담당하는 게 싱글 쓰레드고 그 외 wep api 요청 같은 거는 다른 쓰레드가 처리를 한다 node라면 libuv의 쓰레드 풀들이 있다. 그래서 따로 처리된 애들을 콜백 큐라는 곳에 넣었다가 엔진 안에 콜 스택이 있는데 거기가 비어있으면 콜백 큐에 있는 태스크 들을 올려준다.
왜 call back큐가 비어야 되는지는 밑에 참고글 읽어보면 좋을 거 같다. 간단히 이야기하면 동기화 문제 때문에 그런다. 그리고 위에서 스택이 비어있는지 확인하고 콜백 큐에 함수가 기다리는지 확인하는 역할을 이벤트 루프라는 애가 하는데 이건 자바스크립트 엔진 안에 포함되어있다. 자세한 부분은 블로그를 참고하면 될 거 같다.
자 그럼 동작방식은 대충 살펴봤고, 실행 컨텍스트는 무엇인가. DeepDive 정의를 보자 (DeepDive의 경우 ES3를 기반)
실행 컨텍스트(Execution Context)는 scope, hoisting, this, function, closure 등의 동작원리를 담고 있는 자바스크립트의 핵심원리이다. 실행 컨텍스트를 바로 이해하지 못하면 코드 독해가 어려워지며 디버깅도 매우 곤란해질 것이다.
ECMAScript 스펙에 따르면 실행 컨텍스트를 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이라고 정의한다. 좀 더 쉽게 말하자면 실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경이라고 말할 수 있겠다. 여기서 말하는 실행 가능한 코드는 아래와 같다.
전역 코드 : 전역 영역에 존재하는 코드
Eval 코드 : eval 함수로 실행되는 코드
함수 코드 : 함수 내에 존재하는 코드
런타임 같은 경우 언어를 실제로 구동하는 환경이고, 실행 컨텍스트는 추상적인 개념으로 코드에 제공할 환경 정보들을 모아놓은 객체로 보면 될 거 같다.
실행 컨텍스트는 3가지 프로퍼티를 소유한다. Variable Object , Scope chain, this Value. 실행 컨텍스트는 처음에 전역 컨텍스트를 만들고 함수를 만나면 차례로 함수 실행 컨텍스트를 만든다. 구체적인 과정인 밑에서 다루고 위의 3가지 프로퍼티를 보자
VO는 변수 객체로 실행 컨텍스트가 생성되면 여러 정보를 담을 객체를 생성한다. Variable Object는 실행 컨텍스트의 프로퍼티이기 때문에 값을 갖는데 이 값은 다른 객체를 가리킨다. 그런데 전역 코드 실행시 생성되는 전역 컨텍스트의 경우와 함수를 실행할 때 생성되는 함수 컨텍스트의 경우, 가리키는 객체가 다르다. 전역은 Global Object를 갖고 함수 컨텍스트의 경우 Activation Object를 갖는다. 아무튼 여기에는 매개변수, 함수 선언, 변수 가 들어간다
스코프 체인은 리스트로 전역 객체와 중첩된 함수의 스코프 레퍼런스를 차례로 저장함. 그니까 해당 함수가 참조할 수 있는 변수 객체를 가리킨다.
마지막으로 this는 할당되는 this값을 가리킨다.
다음으로 실행 콘텍스트 과정을 볼 텐데 기니까 간단하게 정리하겠다. 전역이나 함수나 실행 컨텍스트 과정은 같다. 먼저 스코프 체인 생성과 초기화를 한다. 그 뒤 변수 객체화를 실행한다. 여기서 VO의 프로퍼티를 set 하는 순서는 매개변수 함수 변수 순이다. 그리고 이 단계에서 우리가 아는 호이 스팅이 일어난다. var는 선언과 동시에 초기화되고, let과 const는 그렇지 못하기 때문에 에러가 난다.
그리고 여기서 생성되는 함수 객체는 [[Scopes]] 프로퍼티를 가지는데 이 프로퍼티는 자신의 실행환경과 자신을 포함하는 외부 함수의 실행 환경과 전역 객체를 가리키는데 이때 자신을 포함하는 외부 함수의 실행 컨텍스트가 소멸하여도 [[Scopes]] 프로퍼티가 가리키는 외부 함수의 실행 환경은 소멸하지 않고 참조할 수 있다. 이것이 바로 그 유명한 클로저다.
그리고 위에서 말한 호이스팅은 함수 선언식에만 해당된다. 함수 표현식은 일반 변수 방식을 따른다.
마지막 과정은 this value 결정인데 전역에서는 this가 전역 객체를 가리킨다. 만약 함수 컨텍스트 였으면 지정해준 객체를 가리킬 것이다. 위의 준비 과정이 끝나고 차례로 코드가 실행되면서 초기화한 변수 값들 할당하고 실제로 선언된 함수를 실행해서 들어간다 그럼 또 여기서 실행 컨텍스트가 만들어진다. 그 뒤는 똑같다.
근데 이거는 위에서 말한 거처럼 ES3 기반이고 ES6에서는 실행 컨텍스트가 조금 달라졌다.
실행 컨텍스트가 일단 2가지로 구성된다. Variable Environment 그리고 Lexical Environment
VE 같은 경우 LE에 담기는 값과 동일하지만 최초 실행 시 스냅샷을 유지하고, VE 복사해서 LE를 만드는 거다. 그 뒤로는 LE를 사용한다고 한다.
LE는 식별자와 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록하는 자료구조이다. LE는 다시 environmentRecord아 outerEnvironmentRecordReference를 갖는데, ER 같은 경우 스코프에 포함된 식별자를 등록하고 바인딩된 값을 관리, OERR은 상위 스코프에 대한 참조 해당 실행 콘텍스트를 생성한 실행 컨텍스트의 LE를 가리킨다. 이 참조값을 이용해 단방향 연결리스트 스코프체인을 구현.
사실 프로토타입을 위해 실행 컨텍스트를 정리해 봤다. ES6 실행 컨텍스트에 대해서는 따로 정리를 안 했었는데 좀 다른 거 같다. 기존 ES3는 실행 컨텍스트마다 스코프 체인이 최종적으로 GO를 가리키는데 ES6는 그전에 것만 가리키는 거 같다. 다음은 프로토타입을 정리해 보겠다.
참고:
https://poiemaweb.com/js-execution-context
https://solveaproblem.dev/javascript-execution-context/
'TIL' 카테고리의 다른 글
JS. 코드 스플리팅 그리고 트리 쉐이킹 (0) | 2022.01.14 |
---|---|
JS. 프로토타입 (0) | 2022.01.14 |
Flux 그전에 MVC (0) | 2022.01.12 |
JS. Iterable과 Iterator (0) | 2022.01.12 |
CSR 과 SSR (0) | 2022.01.12 |