관리 메뉴

Jerry

#11. JavaScript는 어떤 언어일까? 본문

CS/Terminology

#11. JavaScript는 어떤 언어일까?

juicyjerry 2025. 3. 15. 16:38
JavaScript는 싱글 스레드(Single-threaded) 기반의 논 블로킹(Non-blocking) 언어로,
이벤트 기반(Event-driven), 비동기 처리(Asynchronous processing), 동시성(Concurrency)을 지원하는 동적 프로그래밍 언어

 

동적 프로그래밍 언어(Dynamic Programming Language)는 실행 시간(runtime)에
타입을 결정하거나, 변수나 객체의 속성 등을 변경할 수 있는 프로그래밍 언어를 말합니다. 

 

 

1. 싱글 스레드 기반(Singled-threaded)

- JS는 기본적으로 하나의 Call Stack을 사용하여 코드를 실행하는 싱글 스레드 언어

- 이 말은 한 번에 하나의 작업만 실행할 수 있음을 의미

- CPU를 활용하는 연산이 길어지면 애플리케이션이 멈추는 문제 발생할 수 있음

 

 

2. 논 블로킹 I/O (Non-blocking I/O)

- 싱글 스레드 단점 극복을 위해 JS는 비동기 및 이벤트 기반 아키텍처를 활용

- 이러한 특성은 Node.js 환경에서 서버 개발에 최적화 되어 있음

- I/O 작업을 블로킹 없이 수행하며, 이를 위해 이벤트 루프와 백그라운드 작업을 활용

 

I/O 작업
- 파일 읽기, 네트워크 요청, 데이터베이스 조회 등

백그라운드 작업
- Web APIs, Node.js의 Libuv

 

 

3. 이벤트 루프와 태스크 큐

- 비동기 처리는 이벤트 루프가 콜백 콜 또는 마이크로태스크 큐를 관리하면서 실행

- 콜백 함수와 프로미스, async/await 이용하여 비동기 처리를 효과적으로 구현

- 마이크로태스크 큐가 일반 태스크 큐보다 먼저 실행

마이크로태스크 큐
- Promise.then(), MutationObserver

일반 태스크 큐
- 일반적인(동기적인) 작업을 의미 
- setTimeout이나 setInterval 

 

 

 

 

 

4. 멀티 스레드처럼 동작하는 방식

- JS 자체는 싱글 스레드지이만, 웹 브라우저 환경과 Node.js 런타임이 백그라운드 작업을 멀티 스레드로 처리하면서 효율적인 비동기 프로그래밍을 가능하게 함

 

웹 브라우저(Web APIs)
- setTimeout, fetch, DOM 이벤트 등은 브라우저 엔진이 백그라운드에서 실행한 후, 이벤트 루프를 통해 태스크 큐에서 실행됨

Node.js 환경
- fs.readFile(), database query 등은 libuv를 통해 OS 레벨의 I/O 스레드를 활용하여 논 블로킹 방식으로 실행됨

 

 

 

5. JS의 주요 활용 분야

- 원래 브라우저에서 실행되는 클라이언트 사이드 언어로 개발됨

- 현재, 프론트엔트/백엔드 모두 널리 사용

 

 

 

 

 

 

 


 

 

 

 

요약

Q. JavaScript는 어떤 언어인가요?

A. "JavaScript는 싱글 스레드 기반의 동적 프로그래밍 언어이며, 비동기 처리를 위해 논 블로킹 모델을 사용합니다.

 

기본적으로 한 번에 하나의 작업만 실행할 수 있는 단일 Call Stack을 사용하지만, 이벤트 루프와 태스크 큐를 활용하여 비동기 처리를 효과적으로 수행합니다. 

 

이를 통해 파일 읽기, 네트워크 요청 같은 I/O 작업을 논 블로킹 방식으로 실행할 수 있습니다. 이러한 특성 덕분에 JavaScript는 웹 브라우저와 Node.js 환경에서 빠른 비동기 처리를 지원하며, 서버 개발에서도 널리 사용됩니다.

 

 

 

 

 

 

 

 

Q. 논 블로킹 모델이 뭔가요?

A. 논 블로킹 모델은 작업 요청 후에 해당 작업이 완료될 때까지 기다리지 않고, 다른 작업을 동시에 처리할 수 있는 방식입니다.

즉, 특정 연산 종료까지 프로세스가 멈추지 않고 계속해서 실행됩니다. 

 

JS와 Node.js에서 논 블로킹 방식은 주로 비동기 함수와 콜백, 이벤트 루프를 활용하여 구현됩니다.

예시로는, 파일 입출력이나 네트워크 요청 -> 즉시 다음 코드 실행 -> 요청 완료되면 나중에 결과 처리하는 콜백 실행

 

// 블로킹 방식 예제
const fs = require("fs");

const data = fs.readFileSync("file.txt", "utf8"); // 파일 읽기 완료될 때까지 대기
console.log(data);
console.log("이 코드가 실행될 때까지 파일 읽기가 완료되어야 합니다.");

 

// 논 블로킹 방식
const fs = require("fs");

fs.readFile("file.txt", "utf8", (err, data) => {
  if (err) throw err;
  console.log(data);
});

console.log("파일 읽기가 끝나지 않았어도 이 코드가 먼저 실행됩니다.");

 

 

 

 

 

 

 

+) Node.js에서 논 블로킹이 중요한 이유

- Node.JS는 싱글스레드 기반이므로 하나의 작업이 오래 걸리면 전체 애플리케이션이 멈출 수 있다. 논 블로킹 I/O 방식 사용하면 여러 개 요청을 동시 처리할 수 있어 서버 기능이 향상된다. 대표적인 논 블로킹 패턴으로는 콜백, 프로미스, aync/await이 있다.

 

 

 

 

 

 

 

 

 

 

+) 논 블로킹 방식이 성능을 높여준다고 했는데, 이벤트 루프가 어떻게 이를 가능하게 하나요?

- 이벤트 루프는 JS 비동기 실행을 관리하는 핵심 메커니즘으로, 논 블로킹 방식 동작을 가능케 합니다.

 

이벤트 루프는 크게 4단계로 동작합니다.

 

1번. 콜스택(호출 스택)

- 현재 실행 중인 함수가 쌓이는 공간입니다. 동기적 코드가 실행 시 스택에서 하나씩 실행됩니다.

 

2번. Web APIs(백그라운 작업)

- setTimout, fetch, fs.readFile 같은 비동기 작업 처리 영역입니다.

이 작업은 JS엔진이 직접 실행하는 것이 아니라, 브라우저(WebAPI)나 Node,js의 Livbuv 엔진이 처리합니다.

 

3번. Callback Queue 

- 비동기 작업 완료 후 해당 작업의 콜백 함수가 여기 저장됩니다.

 

4번. Event Loop

- 호출 스택이 비어 있는 상태가 되면, 콜백 대기열에서 대기 중인 작업을 콜백 큐에서 대기 중인 작업을 스택으로 이동시켜 실행합니다.

 

이런 이벤트 루프 덕분에 JS는 단 하나의 스레드만 사용하면서도,
네트워크 요청이나 파일 읽기 같은 시간이 걸리는 작업을 백그라운드에서 처리하고 완료된 후,
다시 메인 스레드에서 실행할 수 있습니다.

 

 

출처: https://images.app.goo.gl/rMdoapi3q4VafvDEA

 

 

 

 

 

 

 

 

 

 

Q. Node.js가 싱글 스레드인데 논 블로킹 처리를 어떻게 효율적으로 하나요?

A. Node,js가 싱글 스레드 기반이지만, 논 블로킹 I/O 처리할 때, 내부적으로 멀티 스레드 풀을 활용합니다.

이 역할을 수행하는 것이 바로 Libuv 라이브러리입니다.

 

Node,js의 주요 논 블로킹 처리 방식에 대해 이야기 해보겠습니다.

1번. 이벤트 루프 기반 비동기 처리

- setTimeout(), setImmediate(), process.nextTick() 등의 비동기 API를 활용하여 작업을 예약합니다.

 

2번. Libuv의 스레드 풀 (Worker Pool)

- 파일 시스템(fs.readFile()), 암호화(crypto.pbkdf2()), 압축(zlib), DNS 조회(dns.lookup()) 같은 작업은 백그라운드에서 스레드 풀을 사용하여 실행됩니다.

 

3번. 비동기 네트워크 요청

- HTTP 요청이나 데이터베이스 쿼리는 OS 커널에서 비동기적으로 처리되므로, 메인 스레드를 차단하지 않고 요청을 동시에 여러 개 보낼 수 있습니다.

 

 

 

 

 즉, Node.js는 싱글 스레드이지만,
I/O 작업을 스레드 풀이나 OS 커널에서
비동기적으로 실행하여 높은 성능을 유지할 수 있습니다."

 

 

 

 

 

 

 

 

 

 

 

Q. 논 블로킹 방식에도 단점이 있나요?

- 네, 항상 장점만 있는 것이 아니라 몇 가지 단점도 존재합니다.

 

1번. 콜백 지옥, 콜백 활용하는 경우가 많아, 코드가 중첩되면서 가독서 떨어지는 문제가 발생할 수 있습니다.

이런 문제 해결을 위해, Promise나 async/await 개선된 비동기 패턴이 등장했습니다.

 

2번. CPU 집중 작업에 부적합

- Node.js는 싱글 스레드 기반이므로, CPU 연산이 많은 작업 (이미지 처리, 대량 데이터 변환) 시, 전체 애플리케이션이 느려질 수 있습니다. 

이런 문제 해결을 위해, Worker Threads 같은 멀티 스레드 모듈 또는 클러스터링 모듈을 활용하여 다중 프로세스 실행할 수 있습니다.

 

3번. 예외 처리 어려움

- 전통적 방식인 try-catch 방식으로 잡을 수 없습니다.

- Promise.catch() 또는 process.on('uncaughtException') 같은 글로벌 예외 처리 핸들러를 활용해야 합니다.

 

 

 

그래서 논 블로킹 방식은 성능을 높이는 강력한 장점이 있지만,
적절한 비동기 패턴과 예외 처리 전략을 함께 적용해야
안정적인 애플리케이션을 개발할 수 있습니다.

 

 

 

 

 

 

 

 

 

 

 

 

Q. Call Stack이 뭔가요?

A. JS 엔진이 현재 실행 중인 함수 호출 순서를 관리하는 자료 구조입니다. 

기본적으로 LIFO 후입선출 구조로 동작합니다.

 

Q. Call Stack이 꽉 차면 어떤 문제가 발생하나요?

A. 스택 오버플로우가 발생하여 프로그램이 강제 종료될 수 있습니다. 

JS는 싱글 스레드 환경에서 실행되므로, Call Stack이 계속 쌓이면 새로운 함수 실행할 수 없어 브라우저나 Node.js 환경에서 에러가 발생합니다.

 

- 무한 재귀 호출, 비효율적 재귀 사용 -> 반복문 사용, 비동기 처리를 통해 해결 

( 재귀 호출을 많이 해야 하는 경우, 어떤 방법으로 성능을 최적화할 수 있을까요?"  질문과 연결)

 

 

 

 

 

 

 

 

 

 

 

 

 

Q. Stack Overflow와 메모리 누수(Leak)는 어떤 차이가 있나요?

A. 둘 다 메모리 관련 문제이지만, 발생 원인과 영향이 다릅니다.

- Stack Overflow는 함수 호출이 너무 깊어 Call Stack 초과시 발생하고, 메모리 누수는 더 이상 사용되지 않는 객체가 해제되지 않을 때 발생됩니다.

 

- Maximum call stack size exceeded 오류나 프로그램 강제 종료 증상이 나타나고, 후자는 메모리 사용량 계속 증가 및 프로그램 속도 느려짐 현상이 나타납니다.

 

- 전자는 무한 재귀 호출이나 깊은 함수 호출 체인,

후자는 이벤트 리스너 미해제전역 변수 불필요 참조, 클로저 내부 참고, DOM 요소 참조 유지 시에 나타날 수 있습니다.

 

- 전자는 반복문이나 비동기 처리를 통해 해결할 수 있고,

후자는 WebMap, WeakSet 사용, 불필요 참조 해제, removeEventLister 호출하여 해결할 수 있습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

Q. JavaScript의 가비지 컬렉션(Garbage Collection)은 어떻게 작동하나요?

A. 자동 메모리 관리 방식으로 동작합니다. 더 이상 필요하지 않은 객체를 자동 해제합니다. 

대표적으로 참조 카운팅(Reference Counting)과 마크 앤드 스위프(Mark-and-Sweep) 알고리즘 사용합니다.

1. 참조 카운팅(Reference Counting)
- 객체가 몇 개의 변수가 참조하고 있는지를 추적합니다. 참조 횟수가 0이 되면 해당 객체를 메모리에서 제거합니다.

2. 마크 앤드 스위프(Mark-and-Sweep)
- 현재 사용 중인 객체와 사용되지 않는 객체를 구별하여, 사용되지 않는 객체를 정리하는 방식입니다.
루트 객체(Root Object, 예: window, globalThis)에서 시작하여 도달할 수 없는 객체를 휴지(garbage)로 간주하고 삭제합니다

 

 

 

 

 

 

 

 

 

 

 

Q. 자바스크립트에서 수동으로 메모리를 관리할 수 있나요? (= Node.js에서 메모리 누수를 방지하는 방법은?)

A. JS는 자동 메모리 관리를 제공하므로, 대부분의 경우 직접 관리할 필요는 없습니다.

일부 특수한 경우에는 메모리 누수 방지를 위해 간접적으로 수동 관리를 할 수 있습니다.

 

1. 강제로 참조 해제하기

- 더 이상 사용되지 않는 객체나 변수의 참조를 null 또는 undefined로 설정

 

2. WeakMap과 WeakSet 사용

- WeakMap**과 WeakSet은 약한 참조(Weak Reference)를 사용하여, 객체가 더 이상 사용되지 않으면 자동으로 메모리에서 해제됩니다.

 

3. 이벤트 리스너 제거

- DOM 요소에 추가된 이벤트 리스너는 해당 요소가 더 이상 사용되지 않아도 메모리를 점유할 수 있습니다. 이벤트 리스너가 필요 없을 때는 반드시 removeEventListener를 사용하여 제거해야 합니다.

 

4. 클로저 참조 해제

- 클로저는 함수가 외부 변수에 대한 참조를 유지하게 하므로, 사용 후 클로저 내부의 변수들을 null로 설정하여 참조를 제거하면 메모리 누수를 방지할 수 있습니다.

 

 

 

 

 

 

 

 

 

 

Q. WeakMap과 Map의 차이는 무엇인가요?

A. 둘 다 키-값 쌍을 저장하는 자료구조지만, 중요한 차이점이 있습니다. 이 차이는 메모리 관리키의 참조 방식에 관련이 있습니다.

키의 참조 강한 참조 (객체도 키로 사용 가능)
모든 데이터 타입(객체, 함수, 기본 타입 등)을 키로 사용
약한 참조 (객체만 키로 사용 가능)
객체만 키로 사용할 수 있습니다. 기본 데이터 타입(문자열, 숫자 등)은 키로 사용할 수 없습니다.
메모리 관리 메모리 누수 가능 가비지 컬렉션이 자동으로 객체를 회수
반복 가능성 가능 불가능
메서드 forEach, clear(), size 등 지원 set(), get(), has()만 지원
성능 객체 참조 유지로 성능 저하 가능 메모리 효율성이 높고, 자동

 

 

 

 

 

 

 

 

 

 

 

Q. 비동기 코드(setTimeout, fetch)는 Call Stack에서 어떻게 실행되나요?

A.

 

  1. 비동기 함수 호출 (setTimeout, fetch) → 콜 스택에서 실행되며, 비동기 작업은 백그라운드로 이동.
  2. 비동기 작업이 완료되면 → 콜백 함수 또는 프로미스 핸들러가 메시지 큐(또는 콜백 큐)로 이동.
  3. 이벤트 루프 → 메시지 큐를 확인하고 콜 스택이 비어 있으면 콜백 함수를 콜 스택으로 이동시킴.
  4. 콜 스택 → 콜백 함수가 실행됨.

 

 

 

 

 

 

 

 

Q. Call Stack과 이벤트 루프의 관계를 설명할 수 있나요?

A.

  • 동기 코드Call Stack에서 실행됩니다. 즉, 함수가 호출되면 순차적으로 Call Stack에 쌓이고 실행됩니다.
  • 비동기 코드(예: setTimeout, fetch)는 실행될 때 웹 API에 의해 처리되며, 그 콜백 함수메시지 큐에 대기하게 됩니다.
  • 이벤트 루프Call Stack이 비어 있는지 체크하고, 비어 있으면 메시지 큐에서 대기 중인 콜백을 Call Stack으로 옮겨 실행합니다.

 

 

 

 

 

 

 

 

Q. 비동기 처리가 뭔가요?

A. 코드의 실행 흐름을 차단하지 않고, 다른 작업이 완료되기를 기다리는 동안 다른 작업을 계속해서 실행할 수 있는 방식입니다.

 

자바스크립트에서는 주로 이벤트 기반으로 비동기 처리를 구현하며, 이는 서버에서의 응답, 파일 읽기, 타이머 등 외부 작업을 다룰 때 매우 유용합니다.

 

 

 

 

 

 

 

 

 

 Q. 그렇다면, 프로미스와 async/await의 차이는 무엇인가요?

A.  두 방식은 본질적으로 같은 비동기 작업을 처리하는 방법을 제공하지만, 표현력가독성에 있어서 차이가 있습니다.

 

1. 프로미스(Promise)

프로미스(Promise)는 비동기 작업을 처리할 때 발생할 수 있는 성공(resolve)과 실패(reject)를 관리하는 객체입니다.

주로 .then(), .catch(), .finally() 메서드를 사용하여 비동기 작업을 처리합니다.

 

2. Async/Await

async/await는 프로미스를 좀 더 직관적이고 동기적으로 작성할 수 있게 해주는 문법적인 설탕(Syntax Sugar)입니다.

async는 함수가 반드시 프로미스를 반환하도록 만들며, await는 프로미스가 처리될 때까지 기다리게 합니다.

 

3. 언제 사용해야 할까?

  • 프로미스(Promise)는 비동기 작업이 여러 개 있을 때 체이닝 방식으로 처리하거나, 프로미스의 메서드들을 잘 활용해야 할 때 적합합니다.
  • Async/Await은 비동기 코드가 여러 번 중첩되거나 복잡할 때, 동기적 코드 스타일을 유지하며 읽기 쉽게 작성하고 싶을 때 적합합니다.

 

 

 

 

 

반응형

'CS > Terminology' 카테고리의 다른 글

#10. LocalStorage vs SessionStorage vs Cookie  (0) 2025.03.13
#9. this 용법  (0) 2025.03.11
#8. SSR vs CSR  (0) 2025.03.10
#7. 호이스팅, 전역 컨텍스트, 클로저  (2) 2025.03.06
#6. REST API vs GraphQL  (0) 2025.03.05