JavaScript 가이드
언어 내부, 런타임, 프레임워크 — 코드 레벨로 동작 원리 정리
언어 내부
이벤트 루프 — setTimeout(fn, 0)이 즉시 실행되지 않는 이유
Call Stack, Task Queue, Microtask Queue의 실행 순서
JavaScript는 싱글 스레드인데 비동기가 되는 이유: 이벤트 루프가 Call Stack이 비었을 때 Task Queue에서 콜백을 꺼내서 실행하기 때문. Promise의 then은 Microtask Queue에 들어가서 일반 Task보다 먼저 실행된다.
클로저와 스코프 체인 — 함수가 외부 변수를 기억하는 원리
Lexical Environment 객체가 만드는 스코프 체인의 내부 구조
클로저는 함수가 생성된 시점의 Lexical Environment를 캡처해서 보존하는 것이다. 함수가 외부 스코프의 변수를 참조하면, 그 Lexical Environment 객체가 가비지 컬렉션되지 않고 살아남는다.
프로토타입 체인 — class가 없던 시절의 상속
__proto__, prototype, Object.create()의 관계
JavaScript의 class는 문법적 설탕이다. 내부적으로 프로토타입 체인으로 동작한다. 객체의 __proto__가 부모 객체를 가리키고, 프로퍼티를 못 찾으면 체인을 따라 올라간다.
Promise 내부 — then()이 Microtask Queue에 들어가는 과정
Promise의 3가지 상태와 thenable 체이닝의 실행 메커니즘
Promise는 pending/fulfilled/rejected 3가지 상태를 가진 상태 머신이다. resolve() 호출 시 then에 등록된 콜백이 Microtask Queue에 들어간다. async/await는 이 Promise의 문법적 설탕.
TypeScript
TypeScript 타입 시스템 — 컴파일 후 전부 사라진다
구조적 타이핑(Structural Typing)이 뭔지, 왜 Java의 명목적 타이핑과 다른지
TypeScript의 타입은 컴파일 시점에만 존재하고, 런타임 JavaScript에는 흔적도 남지 않는다. Java와 달리 "이름"이 아닌 "구조"로 타입 호환성을 판단한다(Structural Typing).
TypeScript 제네릭 패턴 — T가 뭔지 한 번에 이해하기
함수/클래스/인터페이스에서 "나중에 결정되는 타입"을 쓰는 법
T는 "아직 모르는 타입"의 자리표시자다. 함수를 호출하는 시점에 T가 구체 타입으로 결정된다. 제네릭 없이는 any를 쓰게 되고, any를 쓰면 TypeScript를 쓰는 의미가 없다.
판별 유니언 — TypeScript에서 enum 대신 쓰는 패턴
type 필드로 분기하면 TypeScript가 각 분기의 타입을 자동으로 좁혀준다
공통 필드(discriminant)로 유니언 타입을 구분하는 패턴. switch/if로 분기하면 TypeScript가 해당 분기에서 타입을 자동으로 좁혀준다. Rust의 enum + match와 비슷한 효과.
Node.js
Node.js 이벤트 루프 — 브라우저와 다른 6단계 페이즈
timers → pending → idle → poll → check → close, 그리고 process.nextTick의 위치
Node.js의 이벤트 루프(libuv)는 브라우저와 다르게 6개 페이즈를 순회한다. setTimeout은 timers 페이즈, setImmediate은 check 페이즈, process.nextTick은 모든 페이즈 사이에 실행.
Node.js Streams — 대용량 데이터를 메모리 폭발 없이 처리하는 법
Readable, Writable, Transform + backpressure의 동작 원리
10GB 파일을 fs.readFile()로 읽으면 메모리가 터진다. Stream은 데이터를 chunk 단위로 흘려보내서 메모리를 일정하게 유지한다. Backpressure는 소비자가 느릴 때 생산자를 멈추는 메커니즘.
ESM vs CommonJS — require와 import가 다른 이유
동기 로딩 vs 비동기 로딩, 그리고 "type": "module"의 의미
CommonJS(require)는 동기 로딩, ESM(import)은 비동기 로딩. Node.js에서 두 시스템이 공존하면서 생기는 호환성 문제와 해결법.
브라우저 API
Web Workers — JavaScript에서 멀티스레드를 쓰는 법
postMessage의 구조화된 복제(Structured Clone)와 SharedArrayBuffer
Web Worker는 메인 스레드와 별도의 스레드에서 JavaScript를 실행한다. DOM 접근 불가, postMessage로 통신. 무거운 계산을 Worker에 넘기면 UI가 안 멈춘다.
Service Worker 생명주기 — install, activate, fetch의 순서
PWA의 핵심, 오프라인 캐시가 동작하는 메커니즘
Service Worker는 브라우저와 네트워크 사이에 프록시로 동작한다. install → activate → fetch 순서로 생명주기가 진행되며, Cache API로 리소스를 저장해서 오프라인에서도 앱이 작동하게 만든다.
Intersection Observer — 스크롤 이벤트 없이 요소 가시성 감지
lazy loading, 무한 스크롤, 애니메이션 트리거의 성능 좋은 구현 방법
Intersection Observer는 요소가 뷰포트에 들어오거나 나갈 때 콜백을 실행한다. scroll 이벤트 + getBoundingClientRect()보다 성능이 좋고, 브라우저가 최적화된 타이밍에 실행해준다.
Popover API란? JS 없이 툴팁 · 메뉴를 여닫는 법
popover 속성 하나로 열기/닫기, Esc 처리, 접근성이 자동으로 동작하는 브라우저 네이티브 UI
기존 툴팁은 이벤트 리스너, 상태 관리, ARIA 속성 수동 동기화가 전부 필요했다. Popover API는 이걸 브라우저 네이티브로 대체한다. popover 속성과 popovertarget만으로 열기/닫기, Esc 키, 키보드 내비게이션이 자동 동작한다.
Cookie · localStorage · sessionStorage — 질문 세 개면 고를 수 있다
같은 화면에서 폴더 트리는 Cookie, 스크롤 위치는 sessionStorage 로 갈린 이유
서버가 읽나 → 첫 HTML 에 반영돼야 하나 → 탭을 닫아도 남아야 하나. 이 순서로 물으면 거의 다 갈린다. 토큰 같은 비밀값은 묻지 말고 HttpOnly Cookie.
event.target vs currentTarget — 아이콘 선 위를 누를 때만 터지던 버그
data-* 를 읽을 땐 currentTarget. SVG 는 칠해진 선만 클릭을 받는다
리스너를 단 요소의 data-* 를 읽을 때는 event.currentTarget 을 쓴다. event.target 은 실제로 눌린 가장 안쪽 요소라서, 버튼 안 SVG 아이콘의 선을 누르면 <path> 가 잡히고 값이 undefined 가 된다.
웹 오디오
setInterval 로 드럼을 치면 박자가 흔들린다 — 「지금 울려」 말고 「몇 초에 울려」
JS 타이머는 깨우기만 하고, 소리는 오디오 시계로 미리 예약하는 lookahead 스케줄러
브라우저에서 박자를 맞추려면 타이머 콜백 안에서 바로 소리를 내지 말고, 25ms 마다 깨어나 앞으로 0.1초 안에 칠 음을 AudioContext 시각으로 예약한다. 같은 방해 조건에서 setInterval 은 114〜136ms 로 흔들렸고, 예약 방식은 0.5초 간격 그대로였다.
이어폰 「왼쪽만」 은 두 가지다 — StereoPanner(옮기기)와 ChannelSplitter(고르기)
pan: -1 은 오른쪽 소리를 버리지 않고 왼쪽으로 합친다. 실측으로 본 차이
이어폰은 받은 L/R 을 틀기만 한다. 웹에서 「왼쪽만」 을 만들 때 StereoPannerNode 의 pan: -1 은 오른쪽 소리를 왼쪽으로 옮기고, ChannelSplitter 는 왼쪽 채널만 골라 오른쪽을 버린다. 둘 다 오른쪽은 무음이지만 왼쪽에 무엇이 들리는지가 다르다.
오디오 테스트는 화면 말고 AnalyserNode 로 — 백그라운드 탭에서 17개가 전부 「무음」 으로 나온 이유
그림은 멈춰도 소리는 난다. 피크 값을 재면 클리핑까지 잡힌다
브라우저 자동화로 오디오 데모를 검사할 때 캔버스 파형을 보면 백그라운드 탭에서 requestAnimationFrame 이 멈춰 전부 무음으로 오판한다. AnalyserNode 의 시간 영역 데이터에서 피크를 재면 소리 유무와 클리핑(1 초과)을 둘 다 잡는다. 실제로 클리핑 3건을 찾았다.
Strudel 은 비트 스케치엔 최고, 곡 완성엔 약하다 — 그리고 lazy import 하면 이펙트가 조용히 빠진다
앱에 붙여 써 본 감상과, 「다음 mousedown」 을 기다리는 오디오 초기화 함정
Strudel 은 반복 패턴을 즉석에서 만지는 데는 강하지만 곡을 끝까지 만들거나 서비스에 넣는 데는 약하다. 앱에 넣을 때 번들(약 659KB)이 커서 클릭 시 dynamic import 했더니, 오디오 초기화가 이미 지나간 mousedown 을 기다리느라 shape 같은 AudioWorklet 이펙트가 에러 표시 없이 빠졌다. mousedown 을 직접 한 번 쏴 주면 고쳐진다.
WASM · 3D
「100% 로컬」 은 미디어 파일 얘기다 — Whisper 와 ffmpeg 를 WASM 으로 밀어 넣은 대가
브라우저 로컬 영상 편집기 rescript 를 직접 돌려 보고 확인한 것
「100% 로컬」 편집기는 영상 · 음성 파일을 서버로 보내지 않는다는 뜻이지 아무것도 안 나간다는 뜻은 아니다. rescript 는 사용 통계와 크래시 리포트가 기본으로 켜져 있었다. 로컬로 만든 대가는 첫 실행 때 모델 다운로드와, 서버가 COOP/COEP 헤더를 보내야 하는 SharedArrayBuffer 요구로 돌아온다.
.blend 는 웹에서 안 열린다 — Blender → 웹은 변환이 아니라 「GPU 가 먹을 것만 남기는 번역」
3D 게임을 만들었는데 모델 파일이 0개인 이유까지
.blend 는 Blender 의 메모리를 거의 그대로 덤프한 파일이라 웹에서 못 연다. glTF 로 export 하면 모디파이어 · 지오메트리 노드 · Cycles 노드 · 물리는 굽히거나 사라지고, 웹은 삼각형 · 버퍼 · PBR 값만 받아 그림을 처음부터 다시 그린다. 우리 3D 숨바꼭질 게임은 걷기를 거리 기준으로 묶어야 해서 GLB 대신 박스를 코드로 조립했다.
오픈소스 분석
background-removal-js란? 브라우저만으로 이미지 배경을 제거해 투명 PNG로 만드는 구조
IS-Net ONNX 모델 + WASM 추론 + Canvas API, 서버 없이 클라이언트에서 완결되는 파이프라인
background-removal-js(imgly)는 이미지 배경을 제거해 투명 PNG로 돌려주는 JavaScript 라이브러리다. IS-Net 모델을 ONNX Runtime WASM으로 브라우저 안에서 돌려 서버가 필요 없다. 디코딩부터 PNG 인코딩까지 5단계를 코드로 본다.
OpenGenerativeUI — Claude의 시각 자료 생성을 오픈소스로 재현한 구조
CopilotKit Deep Agents + LangGraph + 샌드박스 iframe, AI가 차트·3D·다이어그램을 실시간 생성하는 파이프라인
CopilotKit이 Claude의 인터랙티브 시각 자료 생성 기능을 오픈소스로 구현했다. Next.js 16 프론트엔드 + Python LangGraph 에이전트가 HTML/SVG/Chart.js/Three.js 코드를 생성하고, 샌드박스 iframe에서 렌더링한다. MIT 라이선스. Star 1.1K.
Pretext — DOM 없이 텍스트 높이를 측정하는 순수 JS 레이아웃 라이브러리
Canvas measureText()로 글자 너비를 가져오고, 이후 줄 계산은 순수 산술 연산만 — 레이아웃 리플로우 0
브라우저에서 텍스트가 몇 줄인지 알아내려면 보통 getBoundingClientRect이나 offsetHeight를 쓴다. 하지만 이 방법은 레이아웃 리플로우를 강제한다. Pretext는 Canvas measureText()로 폰트 엔진에서 직접 글자 너비를 가져오고, 이후 줄 계산은 캐싱된 너비값으로 순수 산술 연산만 수행. DOM에 전혀 접근하지 않는다. 500개 텍스트 배치 기준 layout()이 0.09ms. React·Relay 창시자 chenglou의 프로젝트. ⭐ 7.1K.