본문으로 건너뛰기
Advertisement

1.2 JS 엔진과 런타임

JavaScript 엔진이란?

JavaScript 엔진은 JavaScript 코드를 읽고, 분석하고, 실행하는 프로그램입니다. 브라우저마다 자체 JavaScript 엔진을 내장하고 있습니다.

엔진브라우저/환경개발사
V8Chrome, Edge, Node.js, DenoGoogle
SpiderMonkeyFirefoxMozilla
JavaScriptCore (Nitro)Safari, WebKitApple
HermesReact NativeMeta

JS 엔진 동작 원리

JavaScript 코드가 실행되는 과정을 단계별로 살펴봅시다.

1단계: 소스 코드 파싱

엔진은 텍스트로 된 JavaScript 코드를 읽어 **토큰(token)**으로 분리합니다.

// 이 코드가
const x = 10 + 20;

// 다음과 같은 토큰으로 분리됨
// [const] [x] [=] [10] [+] [20] [;]

2단계: AST(추상 구문 트리) 생성

토큰들을 트리 구조의 **AST(Abstract Syntax Tree)**로 변환합니다.

// const x = 10 + 20; 의 AST (간략화)
{
type: "VariableDeclaration",
kind: "const",
declarations: [{
type: "VariableDeclarator",
id: { type: "Identifier", name: "x" },
init: {
type: "BinaryExpression",
operator: "+",
left: { type: "Literal", value: 10 },
right: { type: "Literal", value: 20 }
}
}]
}

3단계: 바이트코드 생성 (인터프리터)

AST를 **바이트코드(bytecode)**로 변환합니다. 바이트코드는 기계어보다 고수준이지만 소스 코드보다 빠르게 실행됩니다.

V8 엔진의 경우 Ignition 인터프리터가 이 역할을 담당합니다.

4단계: JIT 컴파일

자주 실행되는 코드(Hot Code)를 감지하면 JIT(Just-In-Time) 컴파일러가 기계어로 컴파일하여 성능을 향상시킵니다.

V8에서는 TurboFan 최적화 컴파일러가 이 역할을 합니다.

소스 코드
↓ 파싱
AST
↓ 컴파일
바이트코드 ←→ 인터프리터(Ignition)로 실행
↓ Hot Code 감지
최적화된 기계어 ← TurboFan이 JIT 컴파일

V8 엔진 심층 분석

V8은 Google이 개발한 고성능 JavaScript 엔진으로, Chrome 브라우저와 Node.js에서 사용됩니다.

V8의 메모리 구조

┌─────────────────────────────────┐
│ V8 힙 메모리 │
├─────────────────────────────────┤
│ New Space (Young Generation) │ ← 새로 생성된 객체
│ (1~8MB) │
├─────────────────────────────────┤
│ Old Space (Old Generation) │ ← 오래된 객체
│ (기본 최대 1.5GB) │
├─────────────────────────────────┤
│ Code Space │ ← 컴파일된 코드
├─────────────────────────────────┤
│ Large Object Space │ ← 큰 객체 (>512KB)
└─────────────────────────────────┘

가비지 컬렉션

V8은 Mark-and-Sweep 알고리즘으로 더 이상 참조되지 않는 객체를 자동으로 메모리에서 해제합니다.

function createData() {
// 함수 실행 후 아래 객체들은 참조 없어짐 → GC 대상
const bigArray = new Array(1000000).fill(0);
const obj = { data: bigArray };
return obj.data[0]; // 숫자 0만 반환
} // bigArray, obj는 GC가 메모리 해제

let result = createData(); // 0
// result = 0이고, bigArray/obj는 이미 해제됨

브라우저 런타임

JavaScript 엔진 단독으로는 DOM 조작이나 HTTP 요청을 할 수 없습니다. 브라우저가 제공하는 Web API와 함께 동작합니다.

브라우저 런타임 구성 요소

┌─────────────────────────────────────────────────┐
│ 브라우저 │
│ ┌─────────────────┐ ┌────────────────────────┐ │
│ │ JavaScript │ │ Web APIs │ │
│ │ Engine │ │ - DOM │ │
│ │ (V8/Spider │ │ - Fetch API │ │
│ │ Monkey 등) │ │ - setTimeout │ │
│ │ │ │ - Canvas API │ │
│ │ ┌───────────┐ │ │ - Web Storage │ │
│ │ │ Call Stack│ │ │ - WebSocket │ │
│ │ └───────────┘ │ └────────────────────────┘ │
│ └─────────────────┘ │
│ ┌─────────────────────────────────────────────┐ │
│ │ 이벤트 루프 + 콜백 큐 │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘

브라우저에서만 사용 가능한 전역 객체

// window 객체 (브라우저의 전역 객체)
console.log(window.innerWidth); // 브라우저 창 너비
console.log(window.location.href); // 현재 URL

// document 객체 (DOM 접근)
const btn = document.querySelector('#myBtn');
btn.addEventListener('click', () => alert('클릭!'));

// navigator 객체 (브라우저/기기 정보)
console.log(navigator.userAgent);
console.log(navigator.language); // 'ko-KR'

// history 객체 (브라우저 히스토리)
history.back();
history.pushState({}, '', '/new-url');

이벤트 루프 (간략 소개)

JavaScript는 싱글 스레드이지만 비동기 처리가 가능합니다. 이벤트 루프가 Call Stack과 Callback Queue를 관리합니다.

console.log('1. 시작');

setTimeout(() => {
console.log('3. setTimeout 콜백 (1초 후)');
}, 1000);

console.log('2. 끝');

// 출력 순서:
// 1. 시작
// 2. 끝
// 3. setTimeout 콜백 (1초 후)

이벤트 루프 상세 내용은 Ch6에서 깊이 다룹니다.


Node.js 런타임

Node.js는 브라우저 밖에서 JavaScript를 실행할 수 있게 해주는 런타임 환경입니다. Ryan Dahl이 2009년 V8 엔진 위에 구축했습니다.

Node.js 구성 요소

┌─────────────────────────────────────────┐
│ Node.js │
│ ┌───────────────┐ ┌─────────────────┐ │
│ │ V8 엔진 │ │ Node.js APIs │ │
│ │ │ │ - fs (파일) │ │
│ │ JavaScript │ │ - http │ │
│ │ 실행 │ │ - path │ │
│ └───────────────┘ │ - os │ │
│ │ - crypto │ │
│ ┌───────────────┐ └─────────────────┘ │
│ │ libuv │ │
│ │ (이벤트 루프 │ │
│ │ + I/O) │ │
│ └───────────────┘ │
└─────────────────────────────────────────┘

libuv란?

libuv는 Node.js의 핵심 라이브러리로, C언어로 작성된 비동기 I/O 라이브러리입니다.

  • 이벤트 루프 구현
  • 파일 시스템 I/O
  • 네트워크 I/O
  • 스레드 풀 (CPU 집약적 작업)

Node.js에서만 사용 가능한 기능

// 파일 시스템 접근 (브라우저에서는 불가)
import { readFileSync, writeFileSync } from 'fs';

const content = readFileSync('./data.txt', 'utf-8');
console.log(content);

writeFileSync('./output.txt', 'Hello Node.js!');

// HTTP 서버 (브라우저에서는 불가)
import { createServer } from 'http';

const server = createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World!');
});

server.listen(3000, () => console.log('서버 실행: http://localhost:3000'));

// process 객체 (Node.js 전역)
console.log(process.version); // Node.js 버전
console.log(process.platform); // 'win32', 'linux', 'darwin'
console.log(process.env.PATH); // 환경 변수

브라우저 vs Node.js 비교

항목브라우저Node.js
전역 객체windowglobal, globalThis
모듈 시스템ESM (import/export)ESM + CJS (require)
DOMOX
파일 시스템XO
HTTP 서버XO
네이티브 모듈Web APIsNode.js APIs
샌드박스O (보안 제한)X (제한 없음)

globalThis로 환경 감지

// 브라우저와 Node.js 모두에서 동작하는 전역 접근
if (typeof window !== 'undefined') {
console.log('브라우저 환경');
console.log(window.location.href);
} else {
console.log('Node.js 환경');
console.log(process.version);
}

// 더 나은 방법: globalThis 사용
console.log(globalThis); // 브라우저: window, Node.js: global

Deno와 Bun: 새로운 JavaScript 런타임

Deno

Ryan Dahl이 Node.js의 설계 실수를 반성하며 만든 새로운 런타임 (2020년 출시).

// Deno - TypeScript 네이티브 지원, URL 기반 임포트
import { serve } from "https://deno.land/std@0.200.0/http/server.ts";

serve((req) => new Response("Hello Deno!"), { port: 8000 });

특징:

  • TypeScript 기본 지원
  • 보안 샌드박스 (명시적 권한 필요)
  • Web API 표준 준수
  • npm 호환 (Deno 1.28+)

Bun

Zig 언어로 작성된 초고속 JavaScript 런타임 (2022년 출시).

# 속도 비교 (간단한 HTTP 서버 기준)
# Node.js: ~10만 req/sec
# Deno: ~12만 req/sec
# Bun: ~20만+ req/sec

특징:

  • Node.js 호환
  • 내장 번들러, 테스트 러너
  • 매우 빠른 실행 속도

고수 팁

V8 최적화를 위한 코딩 패턴

V8 엔진은 Hidden ClassInline Cache 기법으로 객체 접근을 최적화합니다. 이를 위해:

// 좋은 예: 항상 같은 순서로 프로퍼티 초기화
class Point {
constructor(x, y) {
this.x = x; // 항상 x 먼저
this.y = y; // 항상 y 다음
}
}

// 나쁜 예: 동적으로 프로퍼티 추가 (Hidden Class 변경)
const p1 = {};
p1.x = 1;
p1.y = 2; // Hidden Class 변경 → 최적화 해제

// 숫자 타입 유지 (V8은 Smi, Double, Object 등 내부 표현 사용)
const arr = [1, 2, 3]; // Smi 배열 (빠름)
arr.push('text'); // Object 배열로 변경 (느려짐)
arr.push(1.5); // Double 배열로 변경

devtools로 엔진 동작 확인

Chrome DevTools의 Performance 탭으로 V8의 JIT 컴파일, GC 동작, 스크립트 실행 시간 등을 시각적으로 확인할 수 있습니다.

Advertisement