아래의 이미지는 자바스크립트 V8 엔진의 구조이다.
콜스택 : 실행 컨텍스트를 저장하는 자료 구조이다. 원시 타입의 데이터가 저장되며, 실행 컨텍스트를 통해 변수 식별자(이름) 저장, 스코프 체인 및 this 관리, 코드 실행 순서 관리 등을 수행한다.
메모리힙 : 참조타입(객체 등) 데이터가 저장되며 구조적이지 않다.
위의 이미지와 같이 주소와 값 형태로 이루어져 있으며 변수 a에 데이터를 할당 시에
변수는 그 데이터를 가진 주소를 가리키게 된다.
또한 변수 b와 값이 데이터 타입이 원시 타입이 아닌 경우,
원시 타입의 값은 그 데이터를 가진 메모리 힙에 주소를 가리키게 되는 것이다.
변수 식별자 a 자체는 콜스택 상의 실행컨텍스트(Execution Context)의 렉시컬 환경(Lexical Environment)이라는 곳에 저장된다.
변수 a에 20을 재할당하면, 본인의 메모리에 있는 값을 변경하는게 아니라, 기존에 20을 저장하고 있는 메모리의 주소값으로 교체한다.
a에 저장된 주소값은 20을 가리키고 있던 b에 저장된 주소값과 동일해진다.
이 때 a가 처음에 가지고 있던 10을 가진 메모리 주소는 쓸모없는 값이 되므로
가비지 컬렉터가 이러한 데이터를 적절한 시점에 제거하게 된다.
만약 여기서 b에 값을 30으로 재할당하면, 30이라는 값을 가진 메모리 공간을 새로 생성 후 b는 그 메모리 공간의 주소를 가리키게 된다.
또한 const의 경우 위와 같은 방식으로 동작이 불가능하다. const는 초기값 할당이 필수이며 주소값을 바꾸는 것이 불가능하기 때문이다.
또한 위에 개념에서 const에서 push와 같은 메소드가 동작하는 이유를 알 수 있다. 메모리힙에 저장된 배열에 값이 추가되는 것일 뿐, 변수가 가리키는 주소가 변하는 것이 아니기 때문이다.
실행 컨텍스트란 실행 코드에 제공할 정보들을 모아 놓은 객체이다.
콜스택에 쌓이며 전역 컨텍스트는 자동으로 먼저 생성되고, 함수 호출 시 함수 컨텍스트가 생성된다.
실행 컨텍스트 (Execution Context) : {
동적인 환경 (Variable Environment) : {…},
어휘적 환경 (Lexica lEnviroment) : {
환경 레코드(Environment Record): {
객체적 환경 레코드(Object Environment Record),
선언적 환경 레코드(Declarative Environment Record): {
함수 환경 레코드(Function Environment Record),
모듈 환경 레코드(Module Environment Record)
},
디스 바인딩(this binding)
},
외부 환경에 대한 참조(Reference to the outer environment)
}
}
전역 컨텍스트는 자바스크립트 실행과 동시에 자동으로 생성되며 함수 컨텍스트는
함수를 호출할 때 생성된다. 또한 실행 컨텍스트는 생성단계와 실행단계로 나누어 생성된다.
(1) 생성(평가) 단계(Creation Phase)
생성단계에서 렉시컬 환경 컴포넌트와 변수 환경 컴포넌트를 생성한다. 렉시컬 환경 컴포넌트는 3가지 일을 하는데 첫번째로 환경 레코드에 식별자 정보를 저장하고 두번째로 외부 렉시컬 환경을 참조하여 스코프 체인을 형성하며 마지막으로 this에 바인딩 될 값을 결정한다.
(2) 실행 단계(Execution Phase)
자바스크립트 엔진이 한줄 한줄 위에서 부터 코드를 읽으면서 코드를 실행하는 단계이다.
이 단계에서 중요한 점은 선언했던 변수들에 값이 할당된다는 것이다.
var a = 1;
function fn2(){
console.log('f2')
}
function fn1(){
console.log('f1')
fn2();
}
fn1();
console.log(a);
var b = 2;
function fn3(){
console.log('f3')
const f4='f4';
function fn4(){
console.log(f4)
}
fn4();
}
fn3();
console.log(b);
- 콜스택의 후입선출방식, 테스트큐의 선입선출방식, 싱글스레드언어(한번에하나의테스크실행), 스코프체인의 결정, this바인딩, 호이스팅의 원리를 알 수 있다.
- js의 이벤트리스너 또한 똑같다. 함수 내부에 이벤트 리스너가 있는 경우 함수가 호출문을 만나면 실행컨텍스트에 쌓였다가 실행컨텍스트가 삭제되며 web API에 보관된다. 이벤트 동작을 실행했을 때 테스크큐로 옮겨지고 이벤트루프를 통해 콜스택이 비어있는지 확인 후 콜스택에 밀어내어 콜백함수를 실행하게 된다.
- 스코프 체인은 호출이 아닌 선언 시에 이미 결정된다.
- 이벤트 루프는 콜스택과 테스크큐를 감시하여 콜스택이 비어있을 경우 테스크큐에 있는 콜백 함수를 콜스택에 밀어넣는 역할을 한다. 이벤트 루프는 자바스크립트 엔진이 아닌 구동환경(브라우저,노드)가 가지고 있는 장치이다.
- 태스크 큐는 javascript 실행환경인 브라우저에 위치하며 태스크 web api를 생성하는 경우에 생성된다.
- 웹 api는 javascript 쓰레드가 아닌 별도의 쓰레드를 가진다. 이 별도의 쓰레드에서 특정 상황에 콜백 함수를 테스크 큐에 넘기는 것이다.
- 실행컨텍스트가 보유한 프로퍼티는 Lexical Environment, Variable Environment가 있는데 실제 사용되는 정보가 담긴 곳은 Lexical Environment이다.
- if, for문과 같이 블록레벨스코프의 경우 EC는 생성하지 않은 채 상위 EC를 그대로 쓰며, block scope만의 LE를 별도로 생성하여 상위 EC가 기존의 LE 대신 새로 생성한 LE를 바라보도록 했다가, block scope가 종료된 시점에 원래의 LE를 복구하는 방식이다.
자바스크립트는 싱글 스레드 언어이다. 동기적 요청을 통해 코드를 한줄 한줄 처리한다.
하지만 이러한 특성으로 인해 콜스택에 실행컨텍스트가 남아있는 동안 브라우저는
아무것도 할 수가 없다. 이러한 문제를 비동기 처리로 해결할 수 있다.
Web API에서 제공하는 setInterval()의 경우 생성단계를 거쳐 실행단계를 지나면
다른 실행컨텍스트와 마찬가지로 콜스택에서 제거된다. 이 때 웹 API에 타이머를 보내고
Web API는 타이머를 재기 시작한다. 타이머가 완료되면 웹 API는 setInterval()이 가지고 있던 콜백 함수를 테스크 큐에 밀어 넣는다. 테스크 큐는 콜스택이 비어있는 것을 보고 테스크 큐에 있는 콜백 함수를 콜스택에 전달하고 콜스택은 함수를 실행한다.
이런 예시를 활용하여 setInterval()의 타이머를 0으로 설정하면 콜스택이
비어있을 때까지 기다렸다가 실행한다는 것을 알 수 있다.
- Web API : 웹 브라우저에서 제공하는 API로 AJAX나 Timeout등의 비동기 작업을 실행
- Task Queue : Callback Queue라고도 하며 Web API에서 넘겨받은 Callback함수를 저장(선입선출 방식)
- Event Loop : Call Stack이 비어있다면 Task Queue의 작업을 Call Stack으로 옮김
참고자료