Node.js
Node.js는 구글 크롬의 자바스크립트 엔진(V8 Engine)에 기반해 만들어진 Javascript 런타임 환경이다. 여기서, 런타임이란 프로그래밍 언어가 구동되는 환경을 말한다.
Javascript의 런타임 환경은 웹 브라우저에만 존재했었다. 이러한 Javascript를 서버단 언어로 사용하기 위해 나온 것이 Node.js이다.
Mac에서 Node.js 설치하기
brew install node
버전 확인
node -v
npm -v
npm
Javascript로 개발된 각종 모듈의 설치, 업데이트, 구성, 제거 과정을 자동화하여 관리해주는 기능
npm 사용하기
npm init
프로젝트 시작할 때 사용하는 명령어이다.
package.json에 기록될 내용을 문답식으로 입력한다.
npm init --yes
npm init -y
package.json이 생성될 때 기본값으로 생성된다.
npm install 패키지 이름
프로젝트에서 사용할 패키지를 설치하는 명령어이다.
설치된 패키지의 이름과 정보는 package.json의 dependencies에 입력된다.
package.json
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
프로젝트에 대한 정보와 사용 중인 패키지 이름 및 버전 정보가 담겨 있는 파일이다.
패키지들이 서로 의존되어 있어, 문제가 발생할 수 있는데 이를 관리하기 위해 필요하다.
"name": 패키지 이름
"version": 패키지의 버전
"main": 자바스크립트 실행 파일 진입점 (Node.js 모듈을 불러올 때 어떤 파일을 진입점으로 사용할지를 지정하는 속성)
"description": 패키지에 대한 설명
"scripts": npm run을 이용해 정해놓은 스크립트 명령어
"license": 해당 패키지의 라이센스
package-lock.json
package.json은 프로젝트의 메타데이터와 설정, 의존성 정보를 담고 있으며 개발자가 직접 수정 가능하고, package-lock.json은 의존성 트리와 정확한 버전을 포함하며 자동으로 생성되고 관리된다.
npm이나 yarn 등의 패키지 매니저가 의존성을 설치하거나 업데이트할 때마다 자동으로 수정된다.
Node.js의 특징
- Javascript 사용
- Single Thread
사용자가 직접 제어할 수 있는 스레드는 하나이다.
- 싱글 스레드이기 때문에 주어진 일을 하나밖에 처리하지 못한다. Non-blocking I/O 기능으로 일부 코드는 백그라운드(다른 프로세스)에서 실행 가능하다.
- 에러를 처리하지 못한 경우 멈춘다.
- 프로그래밍 난이도가 쉽고, CPU, 메모리 자원을 적게 사용한다.
- Thread(스레드): 프로세스 내에서 실행되는 흐름의 단위. 하나의 프로세스에는 n개의 스레드가 존재하며 동시에 작동할 수 있다.
- 프로세스: 실행 중인 프로그램, 운영체제에서 할당하는 작업 단위.
- Non-blocking I/O
특정 작업이 완료될 때까지 기다리지 않고 다른 작업을 계속할 수 있는 방식
기존의 I/O 방식인 Blocking I/O(동기 I/O)와는 달리, 작업이 완료될 때까지 스레드 또는 프로세스가 블로킹되지 않으며, 작업을 동시에 처리할 수 있다. ➡️ 더 많은 작업을 동시에 처리하여 시스템의 성능과 확장성을 향상시킬 수 있다.
- I/O(Input/Output): 컴퓨터 시스템에서 데이터를 입력하고 출력하는 것
- Blocking I/O: I/O 작업이 완료될 때까지 해당 스레드 또는 프로세스가 대기 상태에 머물러야 하기 때문에, 다른 작업을 병렬로 처리하는 데 어려움이 있다.
동기(Synchronous)
- 데이터의 요청과 결과가 한 자리에서 동시에 일어나는 것
- 시간이 얼마나 걸리든지 요청한 자리에서 결과가 주어진다.
- 한 요청에 서버의 응답이 이루어질 때까지 계속 대기해야 한다.
비동기(Asynchronous)
- 데이터의 요청과 결과가 동시에 일어나지 않는 것
- 요청한 후 응답을 기다리지 않고 다른 활동을 한다.
➡️ 비동기 작업은 오래 걸리는 작업에 적합하며, 동기 작업은 작업의 순서와 상태를 명확하게 표현할 때 유용하다.
- 비동기적 Event-Driven
Event-Driven: 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 것
- 이벤트 리스너(Event Listener): 이벤트 등록 함수
- 콜백 함수(Callback Function): 이벤트가 발생했을 때 실행되는 함수
Event Loop
Javascript는 단일 스레드 기반의 언어로 한 순간 하나의 작업만을 처리할 수 있지만, 비동기로 동작하기 때문에 단일 스레드임에도 불구하고 동시에 많은 작업을 수행할 수 있다.
하지만 Javascript 언어 자체가 비동기 동작을 지원하는 것은 아니라고 한다. 비동기로 동작하는 핵심 요소는 브라우저가 가지고 있다. 브라우저는 Web APIs, Event Table, Callback Queue, Event Loop 등으로 구성된다.
- Heap: 메모리 할당이 발생하는 곳
- Call Stack: 실행된 코드의 환경을 저장하는 자료구조, 함수 호출 시 Call Stack에 push된다.
- Web APIs: DOM, AJAX, setTimeout 등 브라우저가 제공하는 API
setTimeout(function exec() {
console.log('second')
}, 1000);
코드가 실행될 때, 각 구성 요소들이 어떤 역할을 하는지 보자.
- Web APIs: setTimeout이 Call Stack에 들어와 실행되면, Browser API인 timer를 호출한다.
- Event Table: 특정 event(timeout, click, mouse move 등)가 발생했을 때, 어떤 callback 함수가 호출되어야 하는지를 알고 있는 자료구조이다. 위 코드에서 호출된 timer가 종료되면 event가 발생하게 되는데, 이때 exec callback 함수가 실행되어야 한다는 것을 Event Table이 알고 있다.
- Callback Queue: 이벤트 발생 시 실행해야 할 callback 함수가 Callback Queue에 추가된다.
Callback Queue는 Task Queue, Microtask Queue, Animation Frames로 이루어져 있다. 다음과 같은 구조가 이루어져 있는 이유는 각각 들어가는 callback 함수들의 종류가 다르고, 들어가는 큐에 따라서 이벤트 루프가 내보내는 우선 순위 역시 달라지기 때문이다.
브라우저마다 이벤트 루프의 탐색 순서가 다를 수 있다고 하는데, 크롬의 기준은 다음과 같다.
Microtask Queue ➡️ Animation Frames ➡️ Task Queue
- Task Queue: 대표적으로 setTimeout 같은 타이머들이 여기에 속한다.
- Microtask Queue: 대표적으로 Promise의 then/catch, process.nextTick가 여기에 속한다.
- Animation Frames: requestAnimationFrame API가 실행되면, callback이 Animation Frames로 담긴다.
- Event Loop
- Call Stack과 Callback Queue를 감시한다.
- Call Stack이 비어있을 경우, Callback Queue에서 함수를 꺼내 Call Stack에 추가한다.
참고 자료
싱글 스레드(Single Thread) vs 멀티 스레드(Multi Thread)