프론트엔드 개발자들이 작업을 하면서 자바스크립트는 땔래야 땔 수가 없는 프로그래밍 언어이다.
그런데 막상 자바스크립트를 사용하면서 작동 원리에 대해 제대로 파악하지 못한 관계로
이번 시간에는 자바스크립트의 작동 원리에 대해 소개한다.
우선 동작원리에 대해 알기 위해서는 내부 구조가 어떻게 되어 있는지부터 파악하는게 좋다.
자바스크립트의 동작 내부 구조는 위와 같이 Memory Heap
, Call Stack
, Callback Queue
로 구축 되어있다.
위 구조는 구글의 V8 Engine의 대표적 구조이다.
const obj = {a: "123"};
let arr = [1, 3, 5];
let arr2 = ["a", "b", "c"];
코드 상 변수를 선언 및 할당하여 바인딩하면 Memory Heap
은 위와 같은 그림처럼 저장되어 있다.
즉, Memory Heap
은 각 변수들의 참조형 데이터(함수, 객체, 배열)들의 메모리 주소와 값들이 저장되어 있다.
Call Stack
의 참조 데이터 값(Value)들은 주소로 저장되어 있으며, 해당 주소들을 통해 Memory Heap
의 주소와 맞으면 값들을 조회할 수 있게 된다.
반면 원시형 데이터들은 Call Stack
에서 그대로 값들이 저장되어 있으므로 Memory Heap
까지 탐색할 필요가 전혀 없다.
이를 통해 예전에 배웠던 얇은 복사와 깊은 복사의 차이가 Memory Heap
, Call Stack
과 깊게 연관되어 있다는 것을 다시 알 수 있다.
let a = 1
console.log(1)
setTimeout(() => {
console.log(2)
}, 0)
console.log(3)
Call Stack
은 실행할 수 있는 코드를 계산 및 모든 변수들의 집합체들이 모여 있으며 처리 방식은 LIFO(Last In First Out)로 공간 요소들이 순차 처리된다.
그러나 참조형 데이터들은 극단적으로 수 만가지 데이터를 저장한다면 Call Stack
이 관리하기 한계가 있어 주소만 저장한 채 앞서 설명한 Memory Heap
에 제대로된 값들이 저장 되었던 것이다.
또한, Call Stack
은 자바스크립트의 메인 역할에 해당되며 단 하나만 존재하는데 이를 통해
자바스크립트는 싱글 스레드 기반의 언어라는 것을 제대로 증명할 수 있게 된다.
싱글 스레드 기반의 언어이므로 위 코드의 출력 결과는 1 2 3
이 출력이 될줄 알겠지만,
결과는 1 3 2
이 출력된다.
분명 싱글 쓰레드 기반이라 1 2 3
이 출력되어야 하는거 아닌지 혼란에 빠질 수도 있다.
마!! 니 싱글 쓰레드인데 내랑 지금 장난치나???
사실 해당 출력결과가 나온 이유에는 CallBack Queue
때문이다.
CallBack Queue
는 일종의 대기실 같은 공간과 비슷한 역할을 수행한다.
해당 대기실에 들어가는 경우는 Web API
3가지로 구성되어 있다.
위와 같은 키워드를 사용하면 queue
에 담겨줘 있으며, 만일 Call Stack
에 존재하던 모든 요소들이 처리가 다 완료 됐을 시 queue
는 FIFO(First In First Out) 방식으로 처리가 된다.
즉 이러한 과정들은 Event Loop
를 통해 처리가 되며, Event Loop
를 쉽게 요약하자면 태스크가 들어오기까지 잠들어 있다가 태스크가 들어오면(queue) 이를 처리하고(stack으로 전달), 처리할 태스크가 없는 경우엔 다시 잠이드는, 끊임없이 돌아가는 자바스크립트 내 Loop이다.
그렇다면 자바스크립트는 Web API
로 인해 싱글 스레드가 아닌 비동기 처럼 수행되기도 하는 프로그래밍 언어이다.
자바스크립트는 위와 같은 특징 덕분에 자바스크립트 런타임 환경에 맞춰 탄생한 Node.js
플랫폼을 통해 서버까지 다룰 수 있게 된다.
그렇다면 메모리 공간(Call Stack
,Memory Heap
) 들은 언제 쯤 제거가 될 지 궁금할 수도 있을 것이다.
더 이상 참조되지 않는 데이터들은 자연스레 소멸 되는데 예시는 아래와 같은 상황에 가비지 컬랙터가 발동된다.
let arr = 10
let arr2 = 20
arr = 20
변수 arr을 20이라는 값으로 재할당될 경우, 값이 변경되는 것이 아닌 arr2의 변수가 있는 메모리 주소를 동일하게 공유하여 20이라는 값을 갖게 된다. 더 이상 10이라는 값은 어느 변수에도 선언되지 않은 관계로 10이라는 값이 저장된 메모리 주소는 제거가 되며 이 과정에서 가비지 컬랙터가 작동한다.
이를 통해 주소와 값들이 자동적으로 제거가 되며, 메모리를 더욱 가볍게 관리한다.
물론 C/C++ 같은 언어 프로그래밍들은 별도로 가비지 컬랙터
를 지정해주어야 메모리 관리를 해주지만, 자바스크립트는 편하게도 함수 실행이 종료되면 자동적으로 메모리를 가비지 컬랙터
로 관리를 해준다.
그러나 개발자들은 절대적으로 자바스크립트의 가비지 컬랙터
를 신뢰해서는 안된다.
let arr = []
while(true) {
arr.push(1)
}
위와 같은 무한 루프에 빠지게 되면 함수를 여전히 실행되며, 메모리 제한 공간을 넘어서 계속 저장되는메모리 누수
가 발생하므로 코드를 작성 시 메모리 관리에 대해 신중하게 생각하며 작성하여야 한다.