JS 동작 원리 (Feat. 그 질문은 좀 삼가 주십시오)

이진혁·2023년 5월 25일
post-thumbnail

얼마 전 개발 스터디에 가입했다!

다른 개발자들과의 소통을 통해 다양한 인사이트를 얻기 위함도 있고

다양한 네트워킹을 통해 꾸준히 개발 관련 활동을 전부터 해오고 싶었기 때문에 가입하게 되었다.

절대 개발 동아리 떨어져서 스터디라도 가입한 거 아니다

스터디 이름은 '그 질문은 좀 삼가 주십시오' ㅋㅋㅋㅋㅋ

줄여서 '그질좀사주'다.

대체 왜 스터디 이름이 저런 지는 모르겠지만 주변 개발자 지인의 추천과 스터디 방장(?)분이 인연이 있는 분이라 바로 가입하게 되었다.

거두절미하고 이번 스터디 주제는 바로 JS 동작원리!

스터디에서 공부하고 익힌 내용들을 확실하게 정리하고 내 것으로 만들기 위해 스터디를 진행할 때 마다 이렇게 글을 남겨보려 한다!

JS는 Single Thread 언어다.

싱글 스레드(Single-thread)란, 프로그램이 동시에 한 번에 하나의 작업만 처리할 수 있는 스레드(Thread) 모델을 의미

JS는 기본적으로 싱글 스레드 기반의 언어이다. Call Stack이 하나라고 표현하기도 한다.

이는 JS 엔진이 단일 스레드에서 코드 실행을 처리하는 것을 의미하는데, 즉 JavaScript 코드는 한 번에 하나의 작업만을 처리할 수 있다는 뜻이다.

쉽게 말하자면 한 번에 한 가지 일 밖에 못한다는 뜻이다!

멀티태스킹이 안 되는 글쓴이와 똑같다고 보면 된다.

나는 하나의 일을 할 때 다른 일을 동시에 하지 못한다.

예를 들어 롤을 하면서 동시에 친구에게 카톡을 보내지 못 하고 카톡을 하고 있으면 롤을 할 수 없는 그런 부류의 사람이다.

JS도 마찬가지라고 보면 된다.

JS는 현재 실행하고 있는 함수가 있는 경우에 다른 일을 할 수가 없고 다른 일들은 멈춰있는 상태가 된다.

만약 브라우저에서 오래 걸리는 작업이 실행될 경우, 웹 페이지의 UI는 멈춰버리고, 위의 사진과 같이 사용자는 어떠한 행동도 할 수 없게 된다.

1분 이상이 시간이 소요되는 오래 걸리는 작업이 있다고 가정 해보자.

자바스크립트는 싱글 스레드 언어이기 때문에, 1분 동안 브라우저는 화면 스크롤, 버튼 클릭 등 아무런 행동을 하지 못하게 된다.

그렇다면 어떻게 해야할까?

이 때 사용하는 것이 바로 비동기 콜백이다.

비동기 콜백을 이해하려면 먼저 JS Engine 과 Web API 그리고 Event Loop 에 대한 사전 지식이 필요하니 알아보자.

JS 코드를 실행시켜주는 우리의 JS Engine

JS 엔진은 JS 코드를 해석하고 실행하는 런타임 환경의 핵심 구성 요소

말 그대로 Javascript 코드를 이해하고 실행을 도와주는 친구다.

대표적인 JS engine으로 V8엔진(Chrome, Node.js에서 사용)이 있으며, 이외에도 각 브라우저 별로 여러가지 엔진들이 존재한다.

JS 엔진은 Memory Heap과 Call Stack으로 이루어져있다.

Memory Heap

메모리 할당이 일어나는 곳으로 참조 타입(객체, 함수, 배열) 데이터가 저장되는 공간.

Call Stack

함수 호출과 관련된 정보를 저장하는 곳으로 코드가 실행되면 코드의 내부의 실행 순서를 기록해 놓고, 하나씩 순차적으로 진행할 수 있도록 도와주는 곳

JS Engine은 꽤나 중요한 부분이라 따로 글을 정리해서 올릴 예정이다.

지금은 간단한게 무슨 역할을 하는 지만 알고 넘어가자!

Web API

Web API(Web Application Programming Interface)는 브라우저 환경에서 JavaScript로 웹 기능에 접근하고 상호작용하는 데 사용되는 API의 집합

Web API는 JavaScript 런타임 환경의 일부로 엔진 영역이 아니다!

보통 아래와 같은 일들을 한다.

  • DOM API :: DOM을 조작
    document.getElementById, addEventListener, document.querySelectorAll 등은 브라우저가 제공하는 DOM API의 일부로, JavaScript를 사용하여 이에 접근할 수 있습니다.
  • AJAX 호출이나 XMLHttpRequestfetch
  • 타이머 함수인 setTimeoutsetInterval

JavaScript 엔진이 어떤 웹 API 메서드를 발견하면, 그 메서드를 이벤트 테이블로 보내고, 그곳에서 이벤트가 발생할 때까지 기다린다.

AJAX 호출의 경우, JS 엔진은 AJAX 호출을 이벤트 테이블로 보내고, AJAX 호출 이후의 코드 실행을 계속한다.

AJAX 호출은 AJAX 호출의 응답이 반환될 때까지 이벤트 테이블에서 대기하게 되는데 setTimeout과 같은 타이머 함수의 경우, 타이머 카운트가 0이 될 때까지 기다린다.

Queue

Queue(큐)는 데이터 요소들을 선입선출(First-In-First-Out, FIFO)의 원칙에 따라 저장하는 자료구조

  • 이벤트 테이블에서 이벤트 큐로 이동한 모든 메시지를 저장.
  • 각 메시지에는 들고있는 함수가 존재.
  • Queue는 메서드가 Queue에 추가된 순서를 유지.

엥 근데 이벤트 테이블이 뭐임?

  • 어떤 이벤트가 발생했을 때 실행해야 하는 비동기 메서드를 저장하는 테이블 데이터 구조를 의미

이벤트가 발생하면 웹 API 콜백이 이벤트 테이블에서 이벤트 큐로 이동한다.

예를 들어, AJAX 호출이 완료되고 응답이 반환되면 이벤트 테이블에서 이벤트 큐로 이동한다.

마찬가지로, setTimeout 메서드의 대기 시간이 0이 되면 이벤트 큐에서 이벤트 테이블로 이동한다.

태스크큐 vs 마이크로태스크큐

잠시 후에 자세히 설명할 Event Loop는 두 가지 종류의 작업 큐, 즉 "태스크 큐(task queue)"와 "마이크로태스크 큐(microtask queue)"를 갖고 있다.

두 큐 모두 비동기 작업이 완료될 때까지 그 작업을 대기시키는 역할을 하지만, 두 큐는 처리 방식과 우선순위에 있어서 몇 가지 차이점이 있다.

태스크큐

태스크 큐는 "매크로태스크(macrotasks)"를 관리합니다. 타이머(setTimeout, setInterval), UI 이벤트, 네트워크 이벤트(fetch의 완료) 등이 이에 해당한다.

이벤트 루프는 태스크 큐에서 가장 오래된 태스크를 하나씩 꺼내어 실행하며, 각 매크로태스크 사이에는 브라우저의 렌더링 작업이 들어갈 수 있다.

마이크로태스크큐

반면에 마이크로태스크 큐는 "마이크로태스크(microtasks)"를 관리한다.

프로미스의 then 콜백, MutationObserver 콜백 등이 이에 해당한다.

이벤트 루프는 현재 실행 중인 매크로태스크가 완료되면 마이크로태스크 큐를 확인하고, 큐에 있는 모든 마이크로태스크를 한 번에 실행한다.

이 때 중요한 점은, 마이크로태스크 큐가 비워질 때까지 모든 마이크로태스크를 실행하며, 이 과정 사이에는 브라우저의 렌더링이 발생하지 않는다는 점이다!

차이점

따라서 가장 큰 차이점은 "우선순위"다.

마이크로태스크는 매크로태스크보다 더 높은 우선순위를 갖는다.

즉, 매크로태스크가 완료되면 이벤트 루프는 먼저 마이크로태스크 큐를 확인하고, 마이크로태스크 큐가 비워질 때까지 모든 마이크로태스크를 실행한다.

그리고 나서야 다음 매크로태스크를 처리하는데 이를 통해 더 빠른 반응성을 제공할 수 있는 작업(예: 프로미스의 then 콜백)은 가능한 한 빨리 처리되고, 시간이 많이 소요되는 작업(예: 타이머 콜백, UI 이벤트)은 그 사이에 끼어들지 못하게 된다.

또한 렌더링과의 관계에서도 차이점이 있다.

매크로태스크 사이에는 브라우저의 렌더링 작업이 들어갈 수 있지만, 마이크로태스크를 처리하는 동안에는 렌더링이 발생하지 않는다.

이는 마이크로태스크가 UI를 업데이트하는 등의 작업을 한 번에 모아서 처리할 수 있게 하여, 불필요한 렌더링을 방지하고 효율성을 높인다.

요약

  • 태스크 큐는 매크로태스크를 관리하며, 이벤트 루프는 태스크 큐에서 가장 오래된 태스크를 하나씩 꺼내어 실행한다.
  • 마이크로태스크 큐는 마이크로태스크를 관리하며, 매크로태스크가 완료되면 이벤트 루프는 마이크로태스크 큐에 있는 모든 작업을 한 번에 실행한다.
  • 마이크로태스크는 태스크보다 더 높은 우선순위를 갖는다.
  • 마이크로태스크를 처리하는 동안에는 브라우저의 렌더링이 발생하지 않는다.

Event Loop

이벤트 루프(Event Loop)는 JavaScript에서 비동기 작업을 처리하고 이벤트를 관리하는 메커니즘

이벤트 루프는 Stack이 비어 있을 때 Queue에서 가장 오래된 작업을 가져와 Stack에 넣어준다.

  • CallStack이 비어있는지 확인
  • Queue에 할 일이 있는지 확인
  • CallStack 비어있으면 일감을 Queue 👉🏻 CallStack으로 이동

마치며

최대한 중요한 부분만 간단히 적으려 했는데 글이 꽤나 길어졌다.

사실 JS 동작 원리에 대해 파고 파면 정말 심화되고 끝이 없지만 간단히 또 최대한 이해해보면서 작성해보았다.

JS 동작 원리를 알고 코드를 작성하는 것과 모르고 짜는 것은 큰 차이가 있다고 생각한다.

원리를 이해하면 디버깅이 용이해지는데, JavaScript 코드가 예기치 않은 방식으로 동작할 때 오류를 신속하게 파악하고 해결할 수 있는 안목이 생긴다.

또한 이벤트 루프와 비동기 작업 처리 방식을 이해하면 블로킹 작업을 피하고 비동기 코드를 효율적으로 작성할 수 있다는 점에서 꼭 알아야 한다고 생각한다.

더욱 더,, 파고 들어서 정확히 이해해야 한다!

profile
개발 === 99%의 노력과 1%의 기도

0개의 댓글