본 도구에서 사용하는 오픈소스 라이브러리

본 도구의 코드에는 1개의 오픈소스 라이브러리가 포함되어 있습니다.

JavaScript 빠른 참조 — 간결한 참고서

현대 JavaScript(ES2022) 문법, 표준 라이브러리와 가장 자주 쓰는 DOM / Node 헬퍼 함수의 빠른 참조 매뉴얼로, 일상 시나리오의 약 80%를 다룹니다.

JS

JavaScript ES2022

ECMAScript · 다중 패러다임 · 동적 · 약타입

추천 학습 경로

먼저 「Hello World와 실행 환경」(Node.js 또는 브라우저 콘솔)을 실행 → 변수, 타입, 흐름 제어에 익숙해지기 → 배열과 객체의 고급 메서드 익히기 → 참조와 복사, 함수와 클로저 이해 → 비동기(Promise/async/await) 집중 학습 → 마지막으로 필요할 때 DOM, 네트워크 fetch, Node 모듈, 빌드와 디버그를 찾아봅니다. FAQ 섹션은 돌아와서 함정을 피할 때 적합합니다.

1.Hello World 와 실행 환경

Node.js 또는 브라우저에서 JavaScript를 실행하고, 스크립트, 모듈, npm 프로젝트 구조를 이해합니다.

console 출력

console.log 는 콘솔에 출력합니다. console.error 는 오류 출력, console.table 은 표 형식으로 표시합니다. 브라우저와 Node 모두에서 사용 가능합니다.

1
2
3
4
5
console.log('Hello, world!');
console.error('something failed'); // 빨간색 오류 출력
console.log('values:', 42, true); // 다중 인자
console.table([{ id: 1 }, { id: 2 }]); // 테이블
// Node:$ node script.js

Node 실행

node 는 .js 파일을 직접 실행합니다. node -e 는 한 줄 표현식을 실행하고, node -p 는 결과를 출력합니다. 브라우저 의존성이 없을 때 우선적으로 사용합니다.

1
2
3
4
5
// $ node script.js # 스크립트 실행
// $ node -e "console.log(1)" # 직접 실행
// $ node -p 1 + 2 # 결과 출력:3
// 대화형 REPL:$ node
// 구문 검사:$ node --check script.js

브라우저 script

HTML 은 <script> 로 JS 를 로드합니다. defer 는 지연 실행(DOM 준비 후), async 는 비동기 로드입니다. module 타입은 ESM 을 활성화합니다.

1
2
3
4
5
6
<!-- 普通脚本(阻塞渲染) -->
<script src="app.js"></script>
<!-- 延迟执行:DOM 解析完后运行 -->
<script src="app.js" defer></script>
<!-- ES 模块 -->
<script type="module" src="app.js"></script>

명령행 인수

Node 에서 process.argv 는 전체 명령행을 포함합니다. argv[0] 은 node, argv[1] 은 스크립트 경로, argv[2] 부터는 인수입니다.

1
2
3
4
5
6
// $ node app.js --name Nick
const args = process.argv.slice(2);
console.log(args); // ['--name', 'Nick']
// 간단한 파싱:
const name = process.argv[process.argv.indexOf('--name') + 1];
console.log('Hello, ' + name);

npm 초기화

npm init 은 package.json 프로젝트를 생성합니다. npm install 은 의존성을 설치하고 node_modules 와 lock 파일을 생성합니다.

1
2
3
4
5
// $ npm init -y # 빠른 초기화
// $ npm install express # 의존성 설치 후 dependencies 에 기록
// $ npm install -D vitest # 개발용 의존성
// $ npm run build # scripts.build 실행
// $ npm ls # 설치된 의존성 확인

package.json

package.json 은 프로젝트를 설명합니다. name/version, main 진입점, scripts 명령, dependencies 의존성.

1
2
3
4
5
6
7
8
9
10
11
{
"name": "my-app",
"version": "1.0.0",
"type": "module", // ESM 모듈
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"test": "vitest run"
},
"dependencies": { }
}

ES Modules

import/export 모듈화. export 는 내보내기, import 는 가져오기. package.json 에서 type: module 을 설정하거나 .mjs 확장자를 사용합니다.

1
2
3
4
5
6
7
// math.js
export const PI = 3.14;
export function add(a, b) { return a + b; }
export default function main() { }
// main.js
import main, { add, PI as pi } from './math.js';
console.log(add(1, 2), pi);

엄격 모드

"use strict" 는 엄격 모드를 활성화합니다. 암묵적 전역 변수를 금지하고, 무음 오류를 예외로 만듭니다. ESM 모듈은 자동으로 엄격 모드입니다.

1
2
3
4
5
6
'use strict';
// 선언 없이 대입 금지:
x = 10; // ReferenceError
// 엄격 모드는 과거의 괴이한 동작을 수정
// ESM / class / 모던 도구는 기본으로 활성화
// 권장:코드 파일 상단에 선언

2.변수와 상수

let/const 선언, 스코프, 동적 타입, 구조 분해와 템플릿 문자열.

let 과 const

let 은 변경 가능한 변수를 선언하고, const 는 상수를 선언합니다(객체 자체는 변경 가능). 블록 스코프. const 를 우선 사용하고, 변경이 필요할 때 let 을 사용합니다.

1
2
3
4
5
6
7
8
let count = 42;
count = 43; // OK:let 은 재할당 가능
const name = 'Nick';
// name = 'X'; // 오류:const 는 재할당 불가
const obj = { a: 1 };
obj.a = 2; // OK:객체 속성은 변경 가능
const arr = [];
arr.push(1); // OK:배열은 변경 가능

var 와 let 의 차이

var 는 함수 레벨 스코프 + 호이스팅, let/const 는 블록 레벨 스코프입니다. 모던 코드는 모두 let/const 를 사용하고 var 의 함정을 피합니다.

1
2
3
4
5
6
7
8
9
if (true) {
var x = 1; // var 는 함수 스코프로 호이스팅
let y = 2; // let 은 블록 내에서만
}
console.log(x); // 1
// console.log(y); // ReferenceError
// var 는 중복 선언 가능、호이스팅 있음:
console.log(z); // undefined(호이스팅)
var z = 3;

스코프

블록 스코프 {}, 함수 스코프, 전역 스코프. 안쪽에서 바깥쪽 변수를 읽을 수 있지만, 바깥쪽에서는 안쪽을 읽을 수 없습니다.

1
2
3
4
5
6
7
8
9
10
let global = 'g';
function outer() {
let outerVar = 'o';
function inner() {
let innerVar = 'i';
console.log(outerVar); // 바깥쪽 접근 가능
console.log(global);
}
// innerVar 는 여기서 보이지 않음
}

동적 타입

JS 변수는 타입 제약이 없습니다. 같은 변수에 임의 타입 값을 할당할 수 있습니다. typeof 는 런타임에 현재 타입을 확인합니다.

1
2
3
4
5
let value = 42; // number
value = 'hello'; // string(허용)
value = { a: 1 }; // object
console.log(typeof value); // 'object'
// 약한 타입:'5' + 3 = '53'、'5' - 3 = 2(암시적 변환)

구조 분해 할당

배열/객체에서 값을 변수로 추출합니다. 객체 구조 분해는 이름 기준, 배열은 순서 기준입니다. 기본값 설정과 중첩 구조 분해가 가능합니다.

1
2
3
4
5
6
7
const { name, age = 0 } = person; // 객체 분해 할당
const [first, , third] = arr; // 배열은 순서대로
const { config: { host = 'localhost' } } = app;
// 이름 변경:
const { name: userName } = person;
// 교환:
[a, b] = [b, a];

명명 규칙

변수/함수는 lowerCamelCase, 상수는 UPPER_SNAKE_CASE, 클래스/컴포넌트는 UpperCamelCase 를 사용합니다. 불리언 변수는 is/has 접두사를 사용합니다.

1
2
3
4
5
6
7
let userId = 1;
const MAX_RETRY = 3;
class UserProfile { }
function fetchData() { }
let isValid = false; // 불리언 접두사
let hasPermission = true;
// 파일은 kebab-case 가 일반적:user-profile.js

호이스팅

var 선언과 함수 선언은 스코프 최상단으로 호이스팅됩니다. let/const 에는 일시적 사각지대(TDZ)가 있어 선언 전에 접근하면 오류가 발생합니다.

1
2
3
4
5
6
7
console.log(foo()); // 함수 선언은 호이스팅되어 사용 가능
function foo() { return 1; }
// console.log(a); // TDZ 오류
let a = 1;
// var 는 undefined 로 호이스팅:
console.log(b); // undefined
var b = 2;

템플릿 문자열

백틱 문자열은 ${} 보간과 여러 줄을 지원합니다. 문자열 결합과 여러 줄 텍스트 작성에 우선 사용하며, + 결합을 피합니다.

1
2
3
4
5
6
7
8
const name = 'Nick';
const age = 30;
const msg = `${name} is ${age} years old`;
const multi = `第一行
第二行
缩进保留`;
const expr = `${1 + 2}`; // '3'
// 태그드 템플릿:tag`...` 고급 활용

3.데이터 타입

원시 타입, 객체, typeof 판별과 타입 변환.

원시 타입

7 가지 원시 타입. string, number, boolean, null, undefined, symbol, bigint. 나머지는 모두 object 입니다.

1
2
3
4
5
6
7
8
const s = 'text'; // string
const n = 42; // number
const b = true; // boolean
const nl = null; // null
const u = undefined; // undefined
const sym = Symbol('id'); // symbol
const big = 10n; // bigint
console.log(typeof s, typeof n, typeof nl);

숫자와 NaN

number 는 정수와 부동소수점을 모두 다룹니다. NaN 은 숫자가 아님을, Infinity 는 무한대를 나타냅니다. NaN 은 어떤 값과도(자기 자신 포함) 같지 않습니다.

1
2
3
4
5
6
7
const n = 3.14;
const int = 42;
Number.isNaN(NaN); // true
Number.isFinite(1 / 0); // false(Infinity)
parseInt('42px', 10); // 42
parseFloat('3.14'); // 3.14
// 부동소수점은 부정확:0.1 + 0.2 !== 0.3

문자열

문자열은 변경할 수 없습니다. 길이는 length, 인덱싱은 [i], 메서드는 toUpperCase/slice/split. 문자 인코딩은 UTF-16 입니다.

1
2
3
4
5
6
7
8
const s = 'hello world';
s.length; // 11
s[0]; // 'h'
s.toUpperCase(); // 'HELLO WORLD'
s.slice(0, 5); // 'hello'
s.split(' '); // ['hello', 'world']
s.includes('lo'); // true
s.startsWith('he'); // true

참 값과 거짓 값

거짓 값. false, 0, "", null, undefined, NaN. 나머지는 모두 참 값입니다. if 조건은 참 값 변환을 거칩니다.

1
2
3
4
5
6
7
8
9
// 거짓 값은 6개뿐:
!!false === false;
!!0 === false;
!!'' === false;
!!null === false;
!!undefined === false;
!!NaN === false;
// 나머지는 모두 참 값:[] 참、{} 참、'0' 참
// 빈 배열은 참 값!if (arr.length) 가 정확

null 과 undefined

undefined 는 정의되지 않음/할당되지 않음을 나타내고, null 은 명시적인 빈 값을 나타냅니다. 존재하지 않는 속성에 접근하면 undefined 를 반환합니다.

1
2
3
4
5
6
7
let a; // undefined
let b = null; // 명시적 빈 값
const obj = {};
console.log(obj.x); // undefined
console.log(obj?.x?.y); // undefined(옵셔널 체이닝)
// 판정:value == null 은 null 과 undefined 를 모두 커버
if (a == null) { /* 빈 값 */ }

Symbol 과 BigInt

Symbol 은 유일한 식별자(객체 비공개 키)이고, BigInt 는 임의 정밀도의 큰 정수로 끝에 n 을 붙입니다. typeof 가 각각 식별합니다.

1
2
3
4
5
6
7
const key = Symbol('id');
obj[key] = 1; // 고유한 속성 키
Symbol('a') === Symbol('a'); // false(고유)
const big = 12345678901234567890n;
big + 10n; // BigInt 연산
// BigInt 는 number 와 혼합 연산 불가:
// 10n + 1 // 오류

typeof 판별

typeof 는 원시 타입을 판별합니다. 배열/Date/null 은 모두 "object" 를 반환하므로 다른 방법으로 구분해야 합니다.

1
2
3
4
5
6
7
8
typeof 'a'; // 'string'
typeof 42; // 'number'
typeof true; // 'boolean'
typeof undefined; // 'undefined'
typeof null; // 'object'(역사적 버그)
Array.isArray([]); // true
// 객체 정확한 판정:
Object.prototype.toString.call([]); // '[object Array]'

타입 변환

명시적 변환. String()/Number()/Boolean(). 암묵적 변환은 종종 함정이 됩니다. + 결합, - 연산.

1
2
3
4
5
6
7
8
9
String(42); // '42'
Number('3.14'); // 3.14
Number('abc'); // NaN
Boolean(''); // false
// 암시적 변환:
'5' + 3; // '53'(+ 는 연결)
'5' - 3; // 2(- 는 숫자 변환)
!!'text'; // true
// 숫자 변환은 안전:Number(null) 은 0

객체 타입

원시 타입을 제외한 모든 것은 객체입니다. 일반 객체, 배열, 함수, Date, Map 등. 객체는 참조 타입입니다.

1
2
3
4
5
6
7
const obj = { a: 1, b: 'x' }; // 일반 객체
const arr = [1, 2]; // 배열
const fn = () => {}; // 함수
const date = new Date(); // 날짜
const map = new Map(); // Map
Object.keys(obj); // ['a', 'b']
// typeof 는 모두 'object' 또는 'function'

4.참조와 복사

값 vs 참조, 얕은 복사와 깊은 복사, 배열과 객체 참조, GC.

값 vs 참조

원시 타입은 값으로 전달(복사)되고, 객체/배열/함수는 참조로 전달(공유)됩니다. 이것은 JS 의 가장 중요한 모델입니다.

1
2
3
4
5
6
7
let a = 5, b = a; // b 는 a 의 복사본
b = 10;
console.log(a); // 5(값 복사)
const objA = { x: 1 };
const objB = objA; // 참조 공유
objB.x = 99;
console.log(objA.x); // 99(같은 객체 참조)

객체 참조

함수 매개변수. 객체 인자는 같은 객체를 공유하므로, 함수 안에서 멤버를 수정하면 외부에 영향을 줍니다. 기본 타입은 영향을 받지 않습니다.

1
2
3
4
5
6
7
function mutate(o) { o.x = 99; }
const obj = { x: 1 };
mutate(obj);
console.log(obj.x); // 99(함수에 의해 변경됨)
function bad(o) { o = {}; } // 재할당은 외부에 영향 없음
// 복사가 필요하면 명시적으로:
// const copy = { ...obj };

얕은 복사

전개 연산자 {...obj} / [...arr] 또는 Object.assign 으로 한 단계 복사를 수행합니다. 중첩된 객체는 여전히 참조를 공유합니다.

1
2
3
4
5
6
const obj = { a: 1, nested: { b: 2 } };
const copy = { ...obj };
copy.a = 99; // 원본에 영향 없음
copy.nested.b = 99; // 원본도 변함(얕은 복사)
const arrCopy = [...arr];
// Object.assign({}, obj) 은 얕은 복사와 동일

깊은 복사

깊은 복사는 모든 레벨을 재귀적으로 복사합니다. structuredClone(모던) 또는 재귀 수기 구현. 함수/순환 참조를 포함하면 특별히 처리해야 합니다.

1
2
3
4
5
6
const deep = structuredClone(obj); // 네이티브 깊은 복사
// 중첩 수정해도 원본에 영향 없음:
deep.nested.b = 1;
console.log(obj.nested.b); // 2
// 구형 환경:
// JSON.parse(JSON.stringify(obj)) 은 함수/undefined 를 잃음

배열 복사

[...arr] 얕은 복사, slice() 복사, Array.from 변환. 중첩 배열(다차원)은 여전히 얕은 복사입니다.

1
2
3
4
5
6
const arr = [1, 2, [3]];
const copy = [...arr]; // 얕은 복사
const copy2 = arr.slice(); // 동일
copy[0] = 99; // 원본 배열은 그대로
copy[2][0] = 99; // 원본 배열도 변함!
// 다차원 깊은 복사:structuredClone(arr)

JSON 직렬화 복사

JSON.stringify/parse 는 깊은 복사의 간단한 방법이지만, undefined/함수/Symbol 을 잃고 순환 참조는 지원하지 않습니다.

1
2
3
4
5
6
const obj = { a: 1, b: { c: 2 }, d: undefined };
const copy = JSON.parse(JSON.stringify(obj));
// copy.d 는 사라짐(undefined 는 직렬화 안 됨)
// 순환 참조는 오류:
// const o = {}; o.self = o;
// JSON.stringify(o) // 오류 발생

가비지 컬렉션

V8 은 참조가 없는 객체를 자동으로 회수합니다. 지역 변수는 함수가 끝나면 해제됩니다. 전역/클로저가 보유하면 수명이 늘어납니다.

1
2
3
4
5
6
7
function create() {
const big = new Array(1000); // 힙 할당
return () => big.length; // 클로저가 big 을 보유
}
const fn = create();
// fn 을 더 안 쓰면 null 로 해제:
// fn = null; GC 가 클로저 체인을 회수

객체 동등 비교

=== 는 객체의 참조를 비교하며 내용을 비교하지 않습니다. 내용이 같은 두 객체도 같지 않습니다. 필드별 비교는 수동으로 깊은 비교를 해야 합니다.

1
2
3
4
5
6
7
8
const a = { x: 1 };
const b = { x: 1 };
a === b; // false(참조가 다름)
const c = a;
c === a; // true(같은 참조)
// 내용 비교는 수동으로:
JSON.stringify(a) === JSON.stringify(b); // true
// 또는 필드별 비교(중첩은 재귀/라이브러리로)

5.제어 흐름

if/else, 반복, switch, 삼항 연산과 논리 단축 평가.

if / else

if/else 는 조건에 따라 분기하며, 조건은 참 값 변환을 거칩니다. else-if 체인으로 다중 분기가 가능합니다.

1
2
3
4
5
6
7
8
9
10
const score = 85;
let grade;
if (score >= 90) {
grade = 'A';
} else if (score >= 60) {
grade = 'B';
} else {
grade = 'F';
}
console.log(grade);

삼항 연산자

cond ? a : b 는 한 줄 조건부 할당입니다. 중첩된 삼항은 가독성이 떨어지므로, 복잡한 로직은 if/switch 를 사용합니다.

1
2
3
4
5
6
const age = 20;
const type = age >= 18 ? 'adult' : 'minor';
// 반환값에도 사용 가능:
return ok ? data : null;
// 연결도 가능:
const v = n > 0 ? 'pos' : n < 0 ? 'neg' : 'zero';

for 반복문

고전적인 for 는 인덱스를 사용합니다. 대부분의 컬렉션 시나리오에서는 for...of 또는 배열 메서드가 더 간결합니다.

1
2
3
4
5
6
for (let i = 0; i < 10; i++) {
console.log(i); // 0..9
}
for (let i = arr.length - 1; i >= 0; i--) {
// 역순 순회(인덱스가 필요할 때)
}

for...of

for...of 는 이터러블 객체(배열, 문자열, Map/Set, NodeList)를 순회합니다. 인덱스가 필요 없습니다.

1
2
3
4
5
6
7
8
9
for (const item of items) {
console.log(item);
}
for (const ch of 'abc') { console.log(ch); }
for (const [key, val] of map) {
console.log(key, val);
}
// 인덱스 필요:
// for (const [i, v] of arr.entries())

for...in 객체

for...in 은 객체의 열거 가능한 키(상속 포함)를 순회합니다. 배열은 for...of 를 사용하고, 객체 키를 순회할 때는 Object.keys 를 사용합니다.

1
2
3
4
5
6
7
const obj = { a: 1, b: 2 };
for (const key in obj) {
console.log(key, obj[key]);
}
// 더 안전:
Object.keys(obj).forEach(k => console.log(k));
// 배열에 for...in 사용 금지(인덱스 문자열을 순회)

while / do-while

while 은 먼저 판단 후 실행하고, do-while 은 최소 한 번 실행합니다. 반복 횟수를 모르는 시나리오에 사용합니다.

1
2
3
4
5
6
7
let i = 0;
while (i < 5) { i++; }
let x = 0;
do {
x++;
} while (x < 3); // 최소 1회는 실행
// 파일 스트림/폴링은 while (true) + break 가 일반적

switch

switch 는 엄격한 등호 === 로 비교합니다. case 가 일치하면 break 까지 실행됩니다. fall-through(break 누락)에 주의하세요.

1
2
3
4
5
6
7
8
9
10
11
12
const day = 3;
switch (day) {
case 1:
console.log('Monday');
break;
case 2:
case 3: // 여러 값 병합
console.log('Tue/Wed');
break;
default:
console.log('other');
}

논리 단락과 옵셔널 체이닝

&& || ?? 단락 평가. ?. 옵셔널 체이닝으로 안전하게 접근, ?? null 병합 (null/undefined일 때만 폴백).

1
2
3
4
5
6
7
const name = user?.name ?? 'guest'; // 안전한 조회+폴백
const ok = value && doSomething(); // 참 값일 때만 실행
const fallback = a || 'default'; // 거짓 값 폴백
// 차이 주의:
// a ?? 'd' null/undefined 에만 폴백
// a || 'd' 모든 거짓 값에 폴백
// ?. 연결:obj?.a?.b?.c

break와 continue

continue는 현재 반복을 건너뛰고, break는 루프를 빠져나가며, 레이블이 붙은 break/continue는 중첩 루프를 제어합니다.

1
2
3
4
5
6
7
8
9
10
for (let i = 0; i < 10; i++) {
if (i % 2 === 0) continue; // 짝수 건너뜀
if (i > 7) break; // 중간에 빠져나감
}
outer:
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (j === 2) continue outer; // 바깥쪽 다음 반복으로
}
}

6.함수와 클로저

함수 선언, 화살표 함수, 매개변수, 클로저, this와 콜백.

함수 선언과 표현식

함수 선언은 호이스팅되고 함수 표현식은 호이스팅되지 않습니다. 화살표 함수는 더 간결한 표현식입니다. 셋의 문법이 다릅니다.

1
2
3
4
function add(a, b) { return a + b; } // 선언(호이스팅)
const sub = function (a, b) { return a - b; }; // 식
const mul = (a, b) => a * b; // 화살표 함수
// 호출은 동일:add(1, 2) / sub(5, 3) / mul(2, 4)

화살표 함수

화살표 함수: 간결한 단축형, 자체 this/arguments 없음, 생성자로 사용 불가. 단일 표현식은 암묵적 반환.

1
2
3
4
5
6
7
8
const square = x => x * x; // 인자 1개면 괄호 생략 가능
const add = (a, b) => a + b;
const nothing = () => console.log('hi');
// 여러 줄 본문은 중괄호 + return:
const calc = (a, b) => {
const sum = a + b;
return sum * 2;
};

매개변수와 기본값

함수 매개변수는 기본값을 설정할 수 있고, rest는 나머지 매개변수를 수집합니다. arguments는 유사 배열 객체입니다 (화살표 함수에는 없음).

1
2
3
4
5
6
7
8
9
10
function greet(name = 'guest') {
return 'Hello, ' + name;
}
greet(); // 'Hello, guest'
function sum(...nums) { // rest 파라미터
return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3); // 6
// 분해 할당 파라미터:
function f({ a, b }) { return a + b; }

클로저

클로저는 함수가 생성 당시의 스코프를 기억하게 합니다. 내부 함수가 외부 변수에 접근할 수 있으며, 외부 함수가 이미 반환되었어도 가능합니다.

1
2
3
4
5
6
7
8
function counter() {
let count = 0; // 클로저가 포획
return () => ++count; // 호출마다 count 공유
}
const c = counter();
c(); // 1
c(); // 2
// 클로저는 바깥 변수 참조를 보유、메모리 소비 주의

this 바인딩

this는 호출 시점에 결정됩니다. 객체 메서드는 객체를 가리키고, 일반 함수는 비엄격 모드에서 전역을 가리키며, 화살표 함수는 외부의 this를 상속합니다.

1
2
3
4
5
6
7
8
9
const obj = {
name: 'Nick',
greet() { return 'Hi ' + this.name; }, // 메서드
arrow: () => this, // 화살표:바깥의 this 상속
};
obj.greet(); // 'Hi Nick'
// this 가 유실됨:
const g = obj.greet; // g() 시점의 this 는 전역
// 수정:g.bind(obj) 또는 화살표로 감싸기 () => obj.greet()

call / apply / bind

call/apply는 즉시 호출하며 this를 지정하고, bind는 this가 바인딩된 새 함수를 반환합니다. apply는 인자를 배열로 전달합니다.

1
2
3
4
5
6
7
8
function info(a, b) { return this.name + a + b; }
const ctx = { name: 'Nick' };
info.call(ctx, 1, 2); // 인자 개별 전달
info.apply(ctx, [1, 2]); // 배열로 전달
const bound = info.bind(ctx, 1); // this + 첫 인자 바인딩
bound(2);
// 배열 메서드 재사용:
Array.prototype.slice.call(arguments)

콜백 함수

콜백은 비동기 작업에 함수로 전달하는 방식입니다. 모던 코드에서는 Promise/async를 우선 사용하세요. 콜백이 다중 중첩되면 콜백 지옥이 됩니다.

1
2
3
4
5
6
7
8
9
function load(cb) {
setTimeout(() => cb('data'), 1000);
}
load(result => console.log(result));
// 콜백 지옥:
load(a => load(b => load(c => {
// 중첩이 깊어 유지보수 어려움
})));
// Promise 로 바꾸면 명확

IIFE 즉시 실행

IIFE (function(){})()는 즉시 실행되어 스코프를 격리합니다. 모던 코드에서는 블록 스코프 {}나 모듈로 대체합니다.

1
2
3
4
5
6
7
8
9
(function () {
const secret = 'private'; // 외부에서 접근 불가
})();
// 현대적 대안:
{
const scoped = 'block scope';
}
// 화살표 IIFE:
(() => console.log('run once'))();

재귀

함수가 자기 자신을 호출하려면 기저 조건이 필요하며, 없으면 스택 오버플로가 발생합니다. 꼬리 재귀 최적화는 대부분의 JS 엔진에서 완전히 지원되지 않습니다.

1
2
3
4
5
6
function factorial(n) {
if (n <= 1) return 1; // 베이스 케이스
return n * factorial(n - 1);
}
factorial(5); // 120
// 깊은 재귀는 RangeError: Maximum call stack 주의

7.문자열 처리

문자열 메서드, 템플릿 문자열, 정규식 매칭, 분할과 문자 처리.

주요 메서드

주요 메서드: length 길이, slice/substring 추출, indexOf 검색, includes 포함 여부, replace 치환, split 분할, trim 공백 제거.

1
2
3
4
5
6
7
8
const s = ' Hello, World ';
s.trim(); // 'Hello, World'
s.indexOf('World'); // 8
s.includes('lo'); // true
s.slice(0, 5); // ' Hell'
s.replace('World', 'JS'); // ' Hello, JS '
s.split(','); // [' Hello', ' World ']
'5'.padStart(3, '0'); // '005'

대소문자와 인코딩

toUpperCase/toLowerCase는 대소문자를 변환합니다. charCodeAt는 코드 포인트를 반환하고, fromCharCode는 그 반대입니다. localeCompare로 비교합니다.

1
2
3
4
5
6
'hello'.toUpperCase(); // 'HELLO'
'HELLO'.toLowerCase(); // 'hello'
'A'.charCodeAt(0); // 65
String.fromCharCode(65); // 'A'
'a'.localeCompare('b'); // -1(사전 순)
// 中文字符注意码点:'中'.codePointAt(0)

Unicode 처리

문자열은 UTF-16 코드 유닛 시퀀스이며, Emoji 등 보충 평면 문자는 2개의 코드 유닛을 차지합니다. 순회는 for...of 또는 Array.from을 사용합니다.

1
2
3
4
5
6
const emoji = '😀';
emoji.length; // 2(코드 유닛)
[...emoji].length; // 1(완전한 문자)
for (const ch of 'ab😀') { console.log(ch); }
// 문자열 뒤집기(emoji 올바르게 처리):
const rev = [...'abc😀'].reverse().join('');

템플릿 문자열 연결

템플릿 문자열의 ${} 보간과 멀티라인이 가장 권장됩니다. 표현식이나 메서드 호출을 중첩할 수 있습니다.

1
2
3
4
5
6
7
const user = { name: 'Nick', age: 30 };
const msg = `${user.name} 今年 ${user.age} 岁`;
const html = `<div>
<p>${user.name}</p>
</div>`; // 여러 줄
// 조건부 삽입:
const tag = `${user.age >= 18 ? '成年' : '未成年'}`;

정규식 기초

정규식 리터럴 /pattern/ 또는 new RegExp. test는 판별, exec는 매칭. g는 전역, i는 대소문자 무시.

1
2
3
4
5
6
7
const re = /\d{3}-\d{4}/;
re.test('123-4567'); // true
re.exec('call 123-4567'); // 매칭 결과
const m = 'abc123'.match(/(\d+)/);
m[0]; // '123'
m[1]; // '123'(그룹)
// 전체 탐색:'a1b2'.matchAll(/\d/g)

정규식 치환

replace는 정규식으로 치환하며 $1로 그룹을 참조합니다. 복잡한 로직은 치환 콜백으로 처리합니다. replaceAll은 모든 리터럴을 치환합니다.

1
2
3
4
5
6
'2024-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$1/$2/$3');
// 마스킹:
'13812345678'.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
// 콜백:
'abc'.replace(/\w/g, ch => ch.toUpperCase()); // 'ABC'
// 전체 치환:'a-b-c'.replaceAll('-', '+')

분할과 결합

split은 구분자로 배열을 분할하고, join은 다시 문자열로 결합합니다. split('')은 글자 단위 분할입니다. 빈 문자열로 분할할 때의 동작에 주의하세요.

1
2
3
4
5
6
7
'a,b,c'.split(','); // ['a','b','c']
['a','b','c'].join('-'); // 'a-b-c'
'hello'.split(''); // ['h','e','l','l','o']
// 개수 제한:
'a,b,c'.split(',', 2); // ['a','b']
// 공백 분할:
' a b '.trim().split(/\s+/); // ['a','b']

숫자를 문자열로

toString 또는 템플릿으로 문자열 변환, toFixed는 소수 자릿수, toLocaleString은 천 단위 구분, padStart는 0으로 채웁니다.

1
2
3
4
5
6
(255).toString(16); // 'ff'
(3.14159).toFixed(2); // '3.14'
(1234567).toLocaleString('en-US'); // '1,234,567'
(42).toString().padStart(5, '0'); // '00042'
// 안전한 부동소수점:
(0.1 + 0.2).toFixed(1); // '0.3'

8.배열과 객체

배열 고차 메서드, Map/Set, 객체 연산과 디스트럭처링.

배열 고차 메서드

map 매핑, filter 필터링, reduce 축약, find 검색, some/every 판별. 원본 배열은 수정하지 않습니다.

1
2
3
4
5
6
7
const nums = [1, 2, 3, 4];
nums.map(n => n * 2); // [2, 4, 6, 8]
nums.filter(n => n % 2 === 0); // [2, 4]
nums.reduce((a, b) => a + b, 0); // 10
nums.find(n => n > 2); // 3
nums.some(n => n > 3); // true
nums.every(n => n > 0); // true

Map

Map은 임의 키 매핑입니다. get/set/has/delete/size. 키로 객체를 사용할 수 있습니다. 반복은 삽입 순서를 유지합니다.

1
2
3
4
5
6
7
8
9
const m = new Map();
m.set('name', 'Nick');
m.set(obj, 'value'); // 객체를 키로
m.get('name'); // 'Nick'
m.has('name'); // true
m.delete('name');
m.size; // 1
for (const [k, v] of m) { console.log(k, v); }
// 초기화:new Map([['a',1],['b',2]])

Set

Set은 중복 제거 컬렉션입니다. add/has/delete/size. 배열 중복 제거와 멤버 판별에 우선 사용됩니다.

1
2
3
4
5
6
7
8
const set = new Set([1, 2, 2, 3]);
set.size; // 3(자동 중복 제거)
set.has(2); // true
set.add(4);
set.delete(1);
// 배열 중복 제거:
const uniq = [...new Set(arr)];
// 순회:for (const v of set) / set.forEach

객체 연산

Object.keys/values/entries로 순회. Object.assign으로 병합. Object.freeze로 동결. in으로 속성을 판별합니다.

1
2
3
4
5
6
7
const obj = { a: 1, b: 2 };
Object.keys(obj); // ['a', 'b']
Object.values(obj); // [1, 2]
Object.entries(obj); // [['a',1],['b',2]]
Object.assign({}, obj, { c: 3 }); // 병합
'a' in obj; // true
Object.freeze(obj); // 변경 불가

reduce 축약

reduce는 배열을 단일 값으로 축약합니다. 누적, 그룹화, 평탄화에 사용합니다. 초기값 매개변수는 선택이지만 제공을 권장합니다.

1
2
3
4
5
6
7
8
9
const nums = [1, 2, 3, 4];
const sum = nums.reduce((acc, n) => acc + n, 0);
// 그룹화:
const byType = items.reduce((acc, it) => {
(acc[it.type] ||= []).push(it);
return acc;
}, {});
// 평탄화:
[[1], [2, 3]].reduce((a, b) => a.concat(b), []);

정렬

sort는 기본적으로 문자열 기준으로 정렬합니다 (함정). 숫자 정렬에는 비교 함수가 필요합니다. sort는 배열을 제자리에서 수정합니다.

1
2
3
4
5
6
[3, 10, 1].sort(); // [1, 10, 3](문자열 순서!)
[3, 10, 1].sort((a, b) => a - b); // [1, 3, 10] 오름차순
[3, 10, 1].sort((a, b) => b - a); // 내림차순
// 객체를 필드로 정렬:
people.sort((a, b) => a.age - b.age);
// 복사 후 정렬:[...arr].sort(...) 는 원본을 건드리지 않음

전개와 병합

전개 연산자 ...는 배열/객체 병합, 복사, 매개변수 전개를 수행합니다. Rest 수집과 Spread 전개는 대칭 관계입니다.

1
2
3
4
5
6
const a = [1, 2], b = [3, 4];
const merged = [...a, ...b]; // [1,2,3,4]
const copy = [...a]; // 얕은 복사
const obj = { ...o1, ...o2 }; // 객체 병합
const max = Math.max(...a); // 인자 펼침
function f(...rest) { } // 인자 수집

메서드 체이닝

map/filter 등이 새 배열을 반환해 체이닝할 수 있습니다. 다량의 연산 시 중간 배열 할당에 주의하세요.

1
2
3
4
5
6
7
const result = people
.filter(p => p.age >= 18)
.map(p => p.name)
.sort()
.join(', ');
// 피하기:체인 안에서 큰 배열을 중복 처리
// 성능:대량 데이터는 for 루프로 대체 가능

배열 홀과 희소 배열

배열 홀(hole)은 undefined와 다릅니다. map은 홀을 건너뜁니다. 밀집 배열은 Array.from이나 fill을 사용합니다.

1
2
3
4
5
6
const holes = [1, , 3]; // 중간에 구멍
holes.map(n => n * 2); // [2, <empty>, 6]
// 조밀한 배열 만들기:
Array.from({ length: 5 }, (_, i) => i); // [0..4]
new Array(5).fill(0); // 전부 0 인 조밀 배열
// 확인:0 in holes 는 false

9.메모리와 성능

클로저와 메모리 누수, WeakMap/WeakSet, 대용량 데이터 처리와 최적화.

클로저 메모리

클로저는 외부 변수를 참조하며 유지합니다. 변수가 더 이상 필요하지 않더라도 그렇습니다. 장기적으로 살아 있는 참조는 GC를 방해합니다.

1
2
3
4
5
6
7
function heavy() {
const big = new Array(1000000);
return function () { return big.length; };
}
const keep = heavy(); // big 이 클로저에 유지됨
// keep 을 안 쓰게 되면 null 로 만들어야 함:
// keep = null; 하지 않으면 big 을 회수할 수 없음

이벤트 리스너 누수

DOM 요소가 제거되어도 리스너가 여전히 참조하거나, 리스너가 외부 객체를 잡고 있으면 누수가 발생합니다. 요소 제거 전에 리스너를 해제하거나 스로틀링하세요.

1
2
3
4
5
6
7
8
9
const btn = document.getElementById('btn');
const handler = () => { /* ... */ };
btn.addEventListener('click', handler);
// 제거하기 전에 해제:
// btn.removeEventListener('click', handler);
// 또는 btn.onclick = null;
// AbortController 로 일괄 해제:
// const ac = new AbortController();
// el.addEventListener('click', fn, { signal: ac.signal })

WeakMap / WeakSet

WeakMap의 키는 약한 참조입니다. 객체에 다른 참조가 없으면 회수됩니다. 연결 데이터, 캐시, 비공개 필드에 적합합니다.

1
2
3
4
5
6
7
8
const wm = new WeakMap();
let obj = {};
wm.set(obj, 'private data'); // 연관시키지만 회수를 막지 않음
obj = null; // 키가 회수되면 엔트리도 자동으로 사라짐
// WeakMap 은 반복 불가、size 없음
// 프라이빗 필드 구현:
// const priv = new WeakMap();
// class A { m() { return priv.get(this); } }

성능 최적화

불필요한 복사와 반복 계산을 피하세요. length를 지역 변수로 캐시하고, DOM 연산을 묶어 처리하며, 고빈도 이벤트는 디바운스/스로틀링하세요.

1
2
3
4
5
6
7
8
9
10
11
// length 캐시:
for (let i = 0, len = arr.length; i < len; i++) { }
// 디바운스(고빈도 발화에서 마지막만 실행):
function debounce(fn, ms) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), ms);
};
}
const onInput = debounce(save, 300);

스택 오버플로

무한 재귀 또는 깊은 재귀는 RangeError: Maximum call stack을 유발합니다. 루프로 바꾸거나 깊이를 제한하세요.

1
2
3
4
5
6
7
8
9
10
// 스택 오버플로 발생:
// function loop() { loop(); }
// loop(); // RangeError
// 깊은 트리 순회는 명시적 스택으로:
const stack = [root];
while (stack.length) {
const node = stack.pop();
stack.push(...node.children);
}
// 또는 꼬리 재귀 / 제너레이터

대용량 데이터 처리

대용량 배열의 reduce/map은 전체 할당으로 메모리를 소모합니다. 배치 처리, 제너레이터의 지연 평가, TypedArray의 바이너리 최적화를 사용하세요.

1
2
3
4
5
6
7
8
9
// 제너레이터로 지연 처리:
function* range(n) {
for (let i = 0; i < n; i++) yield i;
}
for (const x of range(1e7)) { /* 필요한 만큼만 */ }
// 바이너리 데이터:
const buf = new Uint8Array(1024);
// 큰 파일은 스트림:
// for await (const chunk of stream) { }

GC와 전역 객체

전역 변수나 캐시는 절대 해제되지 않습니다. 무제한 캐시를 피하고 적시에 정리하세요. 모던 V8의 마크-스윕이 자동 처리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 전역 캐시는 계속 살아있음:
globalThis.cache = {}; // 절대 회수되지 않음
// 유한 캐시:
function lruCache(limit) {
const map = new Map();
return {
get(k) { return map.get(k); },
set(k, v) {
map.set(k, v);
if (map.size > limit) {
map.delete(map.keys().next().value);
}
},
};
}

메모이제이션 캐시

Map으로 순수 함수의 계산 결과를 캐시하여, 같은 인자는 캐시를 즉시 반환하고 비싼 중복 계산을 피하세요. 캐시 점유에 주의하세요.

1
2
3
4
5
6
7
8
9
10
11
12
function memoize(fn) {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const value = fn(...args);
cache.set(key, value);
return value;
};
}
const fib = memoize(n => (n <= 1 ? n : fib(n - 1) + fib(n - 2)));
fib(40); // 순식간(캐시로 가지치기)

10.객체 지향

class 문법, 상속, 캡슐화, 정적 멤버와 프로토타입 체인.

class 정의

class는 객체 템플릿을 정의합니다. constructor 생성, 메서드, 속성. new로 인스턴스를 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
class Person {
constructor(name, age) {
this.name = name; // 인스턴스 속성
this.age = age;
}
greet() { // 인스턴스 메서드
return 'Hi, ' + this.name;
}
}
const p = new Person('Nick', 30);
p.greet(); // 'Hi, Nick'

상속 extends

extends로 상속하고, super()는 부모 생성자를, super.method()는 부모 메서드를 호출합니다.

1
2
3
4
5
6
7
8
9
10
11
class Animal {
constructor(name) { this.name = name; }
speak() { return '...'; }
}
class Dog extends Animal {
constructor(name) { super(name); }
speak() { return 'Woof'; } // 오버라이드
}
const d = new Dog('Rex');
d.speak(); // 'Woof'
d.name; // 'Rex'(상속받은 속성)

getter / setter

get/set은 접근자 속성을 정의합니다. 읽기/쓰기 시 로직을 실행하고 검증을 추가합니다. 메서드가 아닌 속성처럼 접근합니다.

1
2
3
4
5
6
7
8
9
10
11
class Account {
constructor() { this._balance = 0; }
get balance() { return this._balance; }
set balance(v) {
if (v < 0) throw new Error('负余额');
this._balance = v;
}
}
const a = new Account();
a.balance = 100; // setter 발동
console.log(a.balance); // 100(getter 발동)

정적 멤버

static 멤버는 인스턴스가 아닌 클래스에 속합니다. 정적 메서드는 유틸리티 함수, 정적 속성은 상수입니다. static {} 정적 블록으로 초기화합니다.

1
2
3
4
5
6
7
8
class MathUtils {
static PI = 3.14; // 정적 속성
static add(a, b) { return a + b; } // 정적 메서드
static { console.log('init'); } // 정적 블록
}
MathUtils.add(1, 2); // 클래스 경유로 호출
MathUtils.PI; // 3.14
// 정적 메서드 안의 this 는 클래스를 가리킴

private 필드

# 접두사로 비공개 필드/메서드 정의: 클래스 외부에서 접근하면 오류 발생. 진정한 캡슐화(관례적 밑줄이 아님).

1
2
3
4
5
6
7
8
9
10
class Counter {
#count = 0; // 프라이빗 필드
#increment() { // 프라이빗 메서드
this.#count++;
}
up() { this.#increment(); return this.#count; }
}
const c = new Counter();
c.up(); // 1
// c.#count // 오류:프라이빗 필드

instanceof 판별

instanceof는 객체가 특정 클래스 또는 그 부모 체인에 속하는지 판별합니다. Object.getPrototypeOf로 프로토타입을 확인합니다.

1
2
3
4
5
6
7
8
class Dog extends Animal { }
const d = new Dog();
d instanceof Dog; // true
d instanceof Animal; // true(부모 클래스 체인)
[] instanceof Array; // true
// 프로토타입 체인:
Object.getPrototypeOf(d) === Dog.prototype; // true
// 다른 iframe 의 배열은 instanceof 실패、Array.isArray 사용

프로토타입 체인

객체는 프로토타입 체인을 통해 메서드를 공유합니다. class는 프로토타입 상속의 문법적 설탕입니다. prototype을 수정하면 모든 인스턴스에 영향을 줍니다.

1
2
3
4
5
6
7
8
function Animal(name) { this.name = name; }
Animal.prototype.speak = function () {
return this.name + ' speaks';
};
const a = new Animal('cat');
a.speak();
// 클래스 메서드는 실제로 prototype 에 올라감:
Person.prototype.greet === p.greet; // true(메서드 공유)

조합이 상속보다 낫다

깊은 상속 체인은 유지보수가 어렵습니다. 객체 조합을 사용하세요: 생성 시 의존성을 전달하고, mixin으로 기능을 결합합니다.

1
2
3
4
5
6
7
8
9
10
11
class Logger {
constructor(logger) { this.logger = logger; }
save() { this.logger.log('saved'); }
}
// mixin:
const withLogging = (cls) => class extends cls {
log(msg) { console.log(msg); }
};
class User { }
const LoggedUser = withLogging(User);
// 조합:생성자 주입으로 의존성을 테스트하기 쉽게

11.예외 처리

try/catch/finally, 오류 타입, 던지기, 비동기 오류 처리.

try / catch / finally

try는 오류가 발생할 수 있는 코드를 담고, catch는 잡아내며, finally는 성공/실패와 관계없이 실행됩니다. 예외는 호출 스택을 따라 전파됩니다.

1
2
3
4
5
6
7
8
try {
const data = JSON.parse(json);
} catch (e) {
console.error('parse failed:', e.message);
} finally {
console.log('cleanup'); // 반드시 실행됨
}
// catch 바인딩 생략:catch { ... }

오류 타입

Error 기본 클래스와 그 서브클래스: TypeError, ReferenceError, RangeError, SyntaxError. instanceof로 구분합니다.

1
2
3
4
5
6
7
8
9
try {
null.prop; // TypeError
undefinedVar; // ReferenceError
} catch (e) {
if (e instanceof TypeError) { }
if (e instanceof ReferenceError) { }
}
// 사용자 정의 오류는 보통 Error 의 서브클래스
// 비동기 오류는 동기 try/catch 에 잡히지 않음

오류 던지기

throw new Error('msg')로 예외를 던집니다. 임의의 값을 던질 수 있지만, 관례적으로 Error 객체를 던집니다(스택 트레이스 포함).

1
2
3
4
5
6
7
8
9
10
11
function divide(a, b) {
if (b === 0) {
throw new Error('除数不能为 0');
}
return a / b;
}
try {
divide(1, 0);
} catch (e) {
console.log(e.message); // '除数不能为 0'
}

사용자 정의 오류

extends Error로 비즈니스 오류를 정의합니다. 식별을 위해 name 속성을 설정하고, message와 스택 트레이스를 보존합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = 'ValidationError';
}
}
try {
throw new ValidationError('邮箱格式错误');
} catch (e) {
if (e instanceof ValidationError) {
// 비즈니스 오류 분기
}
}

비동기 오류

Promise/async의 오류는 .catch 또는 try/catch(await 내부)로 처리합니다. 동기 try/catch로는 비동기에서 던져진 오류를 잡을 수 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
async function load() {
try {
const res = await fetch(url); // 비동기
return await res.json();
} catch (e) {
console.error('load failed', e);
return null;
}
}
// 또는 Promise 스타일:
fetch(url)
.then(r => r.json())
.catch(e => console.error(e));

처리되지 않은 오류

Promise에 catch가 없으면 unhandledrejection이 발생합니다. 최상위에서 uncaughtException/unhandledrejection에 대한 전역 리스너를 등록합니다.

1
2
3
4
5
6
7
8
9
10
// Node 전역:
process.on('uncaughtException', e => {
console.error('uncaught', e);
});
process.on('unhandledRejection', reason => {
console.error('unhandled rejection', reason);
});
// 브라우저:
window.addEventListener('unhandledrejection', e => { });
// 일반적인 개발:모든 async 에 try/catch 나 .catch 연결

오류 격리

일괄 처리 시 단일 실패가 전체를 중단해서는 안 됩니다. 항목별로 try/catch를 적용하거나, catch 후 기록하고 계속 진행합니다.

1
2
3
4
5
6
7
8
9
10
11
const results = [];
for (const item of items) {
try {
results.push(process(item));
} catch (e) {
console.error('skip item:', item, e);
results.push(null); // 실패를 표시하고 계속
}
}
// 무조건 catch 로 오류를 삼키지 않기:최소한 로그
// Promise 일괄은 Promise.allSettled 로 실패를 격리

오류 원인 cause

ES2022에서 추가된 Error cause 옵션은 오류를 래핑할 때 하위 원인과 스택 트레이스를 보존하여 단계별 디버깅을 용이하게 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function parse(data) {
try {
return JSON.parse(data);
} catch (e) {
throw new Error('解析失败', { cause: e });
}
}
try {
parse('not-json');
} catch (e) {
console.log(e.message); // '解析失败'
console.log(e.cause); // 원래의 파싱 오류
}

12.파일과 IO

Node 파일 시스템, 콘솔 입력, JSON 읽기/쓰기, 그리고 브라우저 DOM IO.

fs 파일 읽기

Node fs/promises로 파일을 읽습니다: readFile은 전체 읽기, readFileSync는 동기 버전입니다. 최신 코드에서는 Promise 버전을 우선 사용하세요.

1
2
3
4
5
6
7
import { readFile, writeFile } from 'node:fs/promises';
const data = await readFile('in.txt', 'utf8');
await writeFile('out.txt', data);
// 동기 버전(간단한 스크립트용):
import fs from 'node:fs';
const s = fs.readFileSync('in.txt', 'utf8');
// 줄 단위:data.split('\n')

fs 쓰기와 추가

writeFile은 덮어쓰기, appendFile은 추가, mkdir은 디렉터리 생성, exists는 존재 여부 확인입니다. rename/cp/rm으로 파일 작업을 수행합니다.

1
2
3
4
5
6
7
import { writeFile, appendFile, mkdir } from 'node:fs/promises';
await writeFile('a.txt', 'content');
await appendFile('a.txt', '\nmore');
await mkdir('data', { recursive: true });
// 존재 확인:
import { access } from 'node:fs/promises';
try { await access('a.txt'); } catch { /* 존재하지 않음 */ }

JSON 파일 읽기/쓰기

JSON 파일 읽기는 parse, 객체 쓰기는 stringify입니다. JSON은 설정과 데이터를 저장할 때 자주 사용됩니다.

1
2
3
4
5
6
7
import { readFile, writeFile } from 'node:fs/promises';
const data = JSON.parse(await readFile('config.json', 'utf8'));
data.updated = true;
await writeFile('config.json', JSON.stringify(data, null, 2));
// 내결함성 파싱:
try { JSON.parse(s) } catch { /* 깨진 JSON 처리 */ }
// 가독성:JSON.stringify(obj, null, 2) 로 들여쓰기

표준 입력/출력

process.stdin은 입력(스트림) 읽기, readline은 줄 단위 상호작용, process.stdout.write는 출력입니다.

1
2
3
4
5
6
7
8
9
10
11
import readline from 'node:readline';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('你的名字?', name => {
console.log('你好', name);
rl.close();
});
// 파이프 입력을 줄 단위로 처리:
// for await (const line of rl) { }

fetch 네트워크 IO

fetch는 원격 리소스를 읽습니다. async/await로 응답을 처리하고, res.json()/res.text()로 파싱합니다. Node 18+에 내장되어 있습니다.

1
2
3
4
5
6
7
8
9
const res = await fetch('https://api.example.com/data');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
// 쓰기 요청:
await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Nick' }),
});

브라우저 DOM IO

DOM으로 페이지 내용을 읽고 씁니다: getElementById로 찾기, textContent는 텍스트, innerHTML은 마크업입니다. 브라우저 환경에서만 사용 가능합니다.

1
2
3
4
5
6
7
const el = document.getElementById('output');
el.textContent = 'Hello'; // 안전한 텍스트
// el.innerHTML = '<p>Hi</p>'; // HTML(XSS 주의)
const input = document.querySelector('input');
const val = input.value; // 입력 읽기
// 이벤트 감시:
btn.addEventListener('click', () => { });

스트리밍 IO

대용량 파일은 스트림을 사용해 메모리에 한꺼번에 읽어들이지 않습니다. createReadStream/createWriteStream, pipe 또는 for await를 사용합니다.

1
2
3
4
5
6
7
8
9
import { createReadStream } from 'node:fs';
const stream = createReadStream('big.log', 'utf8');
for await (const chunk of stream) {
processChunk(chunk); // 청크 단위 처리
}
// 파이프:
// createReadStream('a').pipe(createWriteStream('b'))
// 브라우저 응답 스트림:
// for await (const c of res.body)

path 경로 처리

node:path로 파일 경로를 처리합니다: join은 결합, resolve는 절대 경로 변환, extname은 확장자, basename은 파일 이름입니다.

1
2
3
4
5
6
7
import path from 'node:path';
path.join('data', 'sub', 'a.txt'); // 'data/sub/a.txt'(크로스 플랫폼)
path.resolve('data'); // 절대 경로
path.extname('a/b.json'); // '.json'
path.basename('/a/b/file.txt'); // 'file.txt'
path.parse('/a/b/file.txt'); // 각 세그먼트의 객체
// 상대:path.relative('/a', '/a/b') // 'b'

13.자주 겪는 함정(FAQ)

JS 개발자가 가장 자주 겪는 함정: == vs ===, this, 비동기, 복사와 truthy.

== 과 ===

==는 암시적 변환을 수행하므로(오류 발생 가능) ===는 엄격한 동등 비교입니다. 항상 ===를 사용하세요. null == undefined인 경우만 예외입니다.

1
2
3
4
5
6
7
8
9
// BAD:암시적 변환으로 예상 밖
1 == '1'; // true
null == 0; // false
[] == false; // true(이상함)
// GOOD:엄격 동등
1 === '1'; // false
// 빈값 판정:value === null || value === undefined
// 또는 value == null(동등)

this 손실

메서드를 변수에 할당하거나 콜백으로 전달하면 this가 더 이상 객체를 가리키지 않습니다. 화살표 함수는 외부의 this를 캡처하며, bind는 명시적으로 바인딩합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:콜백에서 this 를 잃음
class A {
init() {
btn.onclick = function () {
this.doSomething(); // this 는 버튼!
};
}
}
// GOOD:화살표 함수
class B {
init() {
btn.onclick = () => this.doSomething(); // this 는 인스턴스
}
}

부동소수점 정밀도

0.1 + 0.2 !== 0.3(이진수 표현이 정확하지 않음). 비교에는 허용 오차를 사용하고, 금액은 정수 센트 단위 또는 라이브러리를 사용하세요.

1
2
3
4
5
6
7
// BAD:부동소수점 직접 비교
0.1 + 0.2 === 0.3; // false
// GOOD:허용 오차 비교
Math.abs((0.1 + 0.2) - 0.3) < 1e-9; // true
// 금액:분 단위 정수로 1990 을 저장、19.9 로 하지 않기
// 또는 toFixed 로 표시:(0.1+0.2).toFixed(2) === '0.30'

비동기 실행 순서

setTimeout과 네트워크 요청은 비동기이며, 코드를 동기 순서로 작성해도 실행 순서가 보장되지 않습니다. await/Promise를 사용하세요.

1
2
3
4
5
6
7
8
9
// BAD:동기 실행으로 착각
let data;
fetch(url).then(r => data = r);
console.log(data); // undefined!
// GOOD:await 로 기다림
const res = await fetch(url);
console.log(res); // 데이터 준비 완료
// 또는 .then 체인 / async 함수

얕은 복사와 깊은 복사

전개 연산자는 한 단계만 복사하며, 중첩된 객체는 여전히 공유됩니다. 중첩 객체를 수정하면 원본 객체에 영향을 줍니다.

1
2
3
4
5
6
7
8
// BAD:스프레드를 깊은 복사로 착각
const b = { ...a };
b.nested.x = 1; // a.nested.x 도 바뀜
// GOOD:깊은 복사
const c = structuredClone(a);
c.nested.x = 1; // a 에 영향 없음
// structuredClone 이 없는 환경은 JSON 이나 직접 재귀

truthy 판별

빈 배열과 빈 객체는 truthy입니다. 배열이 비었는지 확인하려면 .length를, 객체가 비었는지 확인하려면 Object.keys().length를 사용하세요.

1
2
3
4
5
6
7
// BAD:if (arr) 는 항상 참
if ([]) { console.log('runs'); } // 빈 배열은 참 값
// GOOD:길이를 확인
if (arr.length > 0) { }
if (Object.keys(obj).length === 0) { /* 빈 객체 */ }
// 문자열:if (str) 로 비어있지 않은 문자열을 확인하는 것은 안전

var 스코프 누수

var에는 블록 스코프가 없으므로, 반복문/if 안에서 선언하면 함수 스코프로 누출됩니다. 반복문 변수는 let을 사용하세요.

1
2
3
4
5
6
7
8
9
// BAD:var 의 누출 + 클로저가 옛 값을 포착
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 전부 3 을 출력
}
// GOOD:let 의 블록 스코프
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 0, 1, 2
}

parseInt 함정

parseInt('08')은 오래된 엔진에서 8진수로 처리되나요? parseInt('3px')는 3을 반환합니다. 엄격한 파싱에는 Number 또는 +를 사용하세요.

1
2
3
4
5
6
7
8
9
// BAD:parseInt 의 느슨한 해석
parseInt('42px'); // 42(의도와 다를 수 있음)
// 옛 엔진의 8진수:
// parseInt('010') 8 이 될 수 있음
// GOOD:명시적 진법 + 엄격
parseInt('010', 10); // 10
Number('42px'); // NaN(엄격)
+'42'; // 42

중첩 삼항 연산

다층 삼항 연산은 읽기 어렵습니다. 복잡한 분기에는 if/else 또는 객체 매핑을 사용하세요.

1
2
3
4
5
6
7
8
9
10
// BAD:중첩 삼항은 읽기 어려움
const v = a ? b ? 'x' : 'y' : 'z';
// GOOD:if/else 나 매핑
let v;
if (a) v = b ? 'x' : 'y';
else v = 'z';
// 또는 맵 테이블:
const map = { x: 'X', y: 'Y' };
const result = map[key] ?? 'default';

객체 속성 변경

객체 구조 분해는 얕은 복사이며, 중첩 속성은 여전히 참조입니다. 원본 객체가 변경되지 않도록 하려면 깊은 복사가 필요합니다.

1
2
3
4
5
6
7
8
// BAD:구조 분해 후 중첩을 변경
const { nested } = obj;
nested.x = 99; // obj.nested.x 도 바뀜
// GOOD:깊은 복사 후 변경
const copy = structuredClone(obj);
copy.nested.x = 99; // 원본 객체는 불변
// 또는 공유를 피하기:복사 전에 완전한 값을 구조 분해

14.비동기와 이벤트 루프

이벤트 루프, Promise, async/await, 타이머와 동시성.

이벤트 루프

JS는 단일 스레드 + 이벤트 루프입니다. 동기 코드가 먼저 실행되고, 비동기 콜백(타이머/IO)은 작업 큐에 들어갔다가 처리됩니다.

1
2
3
4
5
console.log('1'); // 동기
setTimeout(() => console.log('2')); // 매크로 태스크
Promise.resolve().then(() => console.log('3')); // 마이크로 태스크
// 출력 순서:1, 3, 2
// 마이크로 태스크가 매크로 태스크에 우선;IO 완료 후 콜백이 대기열에 들어감

Promise

Promise는 비동기 결과를 나타냅니다. then은 성공 처리, catch는 실패 처리, finally는 마무리입니다. 체이닝으로 콜백 지옥을 피합니다.

1
2
3
4
5
6
7
8
const p = new Promise((resolve, reject) => {
setTimeout(() => resolve('data'), 1000);
});
p.then(data => console.log(data))
.catch(err => console.error(err))
.finally(() => console.log('done'));
// resolve 성공、reject 실패
// then 은 새 Promise 를 반환해 연결 가능

async / await

async 함수는 Promise를 반환하고, await는 결과를 기다리며 동기 코드처럼 작성되게 합니다. try/catch로 비동기 오류를 잡습니다.

1
2
3
4
5
6
7
8
9
10
11
12
async function load() {
try {
const res = await fetch(url); // 대기
return await res.json();
} catch (e) {
console.error(e);
return null;
}
}
const data = await load();
// await 는 async 함수나 최상위 모듈에서만
// async 함수는 항상 Promise 를 반환

타이머

setTimeout은 지연 실행, setInterval은 주기 실행, clearTimeout/clearInterval은 취소입니다. 지연 단위는 밀리초입니다.

1
2
3
4
5
6
7
const t = setTimeout(() => console.log('once'), 1000);
clearTimeout(t); // 취소
const id = setInterval(() => tick(), 500);
clearInterval(id); // 정지
// 지연 0 도 비동기:
setTimeout(() => console.log('async'), 0);
// Node:setImmediate 는 매크로 태스크、process.nextTick 은 마이크로 태스크

Promise 조합

Promise.all은 모두 성공해야 성공, allSettled는 모두 완료된 후 각자의 상태를 보고, race는 먼저 완료된 것이 승리합니다.

1
2
3
4
5
6
7
8
const p1 = fetch('/a');
const p2 = fetch('/b');
// 병렬로 전부 대기(하나라도 실패하면 즉시 실패):
const [a, b] = await Promise.all([p1, p2]);
// 단일 실패로 중단하지 않음:
const results = await Promise.allSettled([p1, p2]);
// results[i].status === 'fulfilled' | 'rejected'
// 경쟁:Promise.race([p, timeout])

Web Worker

Worker는 독립 스레드에서 시간이 오래 걸리는 코드를 실행해 UI를 차단하지 않습니다. postMessage로 통신합니다. Node에서는 worker_threads를 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
// 메인 스레드:
const w = new Worker('worker.js');
w.postMessage('start');
w.onmessage = e => console.log('result', e.data);
// worker.js:
onmessage = e => {
const result = heavyCompute(e.data);
postMessage(result);
};
// Node:
// import { Worker } from 'node:worker_threads'

동시성 제어

일괄 비동기 작업의 동시 실행 수를 제한해 리소스 고갈을 방지합니다. 배치 처리 또는 세마포어를 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function mapLimit(items, limit, fn) {
const results = [];
const running = [];
for (const item of items) {
const p = fn(item).then(r => {
results.push(r);
running.splice(running.indexOf(p), 1);
});
running.push(p);
if (running.length >= limit) {
await Promise.race(running);
}
}
await Promise.all(running);
return results;
}
// 분할:for (let i = 0; i < items.length; i += limit)

마이크로태스크와 매크로태스크

마이크로태스크(Promise.then, queueMicrotask)는 매크로태스크(타이머)보다 우선합니다. 같은 사이클 내에서 마이크로태스크가 먼저 비워집니다.

1
2
3
4
5
queueMicrotask(() => console.log('micro'));
setTimeout(() => console.log('macro'), 0);
// 출력:micro, macro
// 순서:동기 → 마이크로 태스크 비우기 → 매크로 태스크
// 매크로 태스크 하나가 끝날 때마다 마이크로 태스크를 확인·비움

15.네트워크와 HTTP

fetch 요청, HTTP 메서드, 오류 처리, JSON, WebSocket, URL.

fetch 기초

fetch는 HTTP 요청을 보내 Promise를 반환합니다. res.ok로 성공을 판별하고, res.json()/text()로 읽습니다. Node 18+와 브라우저에 내장되어 있습니다.

1
2
3
4
5
6
const res = await fetch('https://api.example.com/users');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const users = await res.json();
// 또는 텍스트:await res.text()
// 상태 코드:res.status(200/404...)
// 헤더:res.headers.get('content-type')

POST와 요청 본문

fetch의 두 번째 인자로 method, headers, body를 설정합니다. JSON 요청 본문은 JSON.stringify가 필요합니다.

1
2
3
4
5
6
7
8
9
10
const res = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token',
},
body: JSON.stringify({ name: 'Nick', age: 30 }),
});
if (!res.ok) throw new Error('create failed');
const created = await res.json();

쿼리 매개변수

URLSearchParams로 쿼리 문자열을 구성합니다. encodeURIComponent는 특수 문자를 인코딩합니다. URL 파싱.

1
2
3
4
5
6
7
8
9
10
11
12
const params = new URLSearchParams({
q: '速查', page: '2',
});
const url = `/search?${params}`; // q=%E9%80%9F%E6%9F%A5&page=2
// 추가:
params.append('sort', 'asc');
params.get('q'); // '速查'
// 단일 값 인코딩:
const q = encodeURIComponent('a&b');
// URL 해석:
const u = new URL('https://a.com/x?y=1');
u.searchParams.get('y'); // '1'

네트워크 오류 처리

fetch는 네트워크 계층에서 실패할 때만 reject되며(연결 끊김), HTTP 4xx/5xx는 오류를 던지지 않으므로 res.ok를 확인해야 합니다. 타임아웃은 AbortController를 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 10000);
try {
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
} catch (e) {
if (e.name === 'AbortError') console.error('超时');
else console.error('网络错误', e);
} finally {
clearTimeout(timer);
}

WebSocket

WebSocket은 전이중 장기간 연결입니다. onopen/onmessage/onclose 이벤트, send로 전송합니다. 실시간 푸시 시나리오.

1
2
3
4
5
6
7
const ws = new WebSocket('wss://example.com/ws');
ws.onopen = () => ws.send('hello');
ws.onmessage = e => console.log('收到', e.data);
ws.onclose = () => console.log('连接关闭');
ws.onerror = e => console.error('错误', e);
// 닫기:ws.close()
// 하트비트:주기적 ping 으로 연결 상태 확인

axios 비교

axios는 인터셉터, 자동 JSON, 더 친화적인 오류 객체를 제공합니다. fetch는 표준이고 axios는 서드파티 라이브러리입니다.

1
2
3
4
5
6
7
8
9
10
11
import axios from 'axios';
// GET:
const { data } = await axios.get('/api/users');
// POST:
await axios.post('/api/users', { name: 'Nick' });
// 인터셉터:
axios.interceptors.request.use(cfg => {
cfg.headers.Authorization = 'Bearer ' + token;
return cfg;
});
// 에러:err.response.status / err.response.data

HTTP 상태 코드

2xx 성공, 3xx 리다이렉트, 4xx 클라이언트 오류, 5xx 서버 오류. 일반적인 코드는 200/201/400/401/404/500.

1
2
3
4
5
6
7
8
9
10
11
const map = {
200: 'OK 成功',
201: 'Created 已创建',
204: 'No Content 无内容',
400: 'Bad Request 参数错误',
401: 'Unauthorized 未认证',
403: 'Forbidden 无权限',
404: 'Not Found 未找到',
500: 'Internal Server Error 服务器错误',
};
console.log(map[res.status] ?? '其他');

파일 업로드 FormData

FormData로 multipart 폼을 구성하고, fetch POST로 파일과 필드를 업로드합니다. 진행률 모니터링은 XMLHttpRequest가 더 편리합니다.

1
2
3
4
5
6
7
8
9
10
11
const file = document.querySelector('input[type=file]').files[0];
const form = new FormData();
form.append('file', file);
form.append('desc', '示例文件');
await fetch('/api/upload', {
method: 'POST',
body: form, // 자동 multipart、Content-Type 을 수동 설정하지 않음
});
// 진행 상황:
// xhr.upload.onprogress = e => pct = e.loaded / e.total
// 다중 파일:for (const f of files) form.append('file', f)

16.날짜와 시간

Date, 타임스탬프, 형식화, 시간대와 타이머.

Date 기본

new Date() 현재 시간, getFullYear/getMonth(0부터 시작)/getDate. month는 0 기반 인덱스입니다.

1
2
3
4
5
6
7
const now = new Date();
now.getFullYear(); // 2024
now.getMonth(); // 0-11(1 월은 0!)
now.getDate(); // 이번 달의 날짜
now.getDay(); // 요일 0-6(일요일이 0)
now.getHours();
now.toISOString(); // UTC ISO 문자열

타임스탬프

Date.now() 밀리초 타임스탬프(UTC 에포크). getTime()도 동일. 시간대를 넘나드는 저장은 타임스탬프 또는 ISO를 사용합니다.

1
2
3
4
5
6
7
Date.now(); // 밀리초 타임스탬프
new Date().getTime(); // 동일
new Date(1700000000000); // 밀리초에서 생성
// 초 단위:Math.floor(Date.now() / 1000)
// 문자열 해석:
new Date('2024-01-01T12:00:00Z');
// 해석 형식은 통일、모호함을 피할 것(예 '01/02')

형식화된 출력

toLocaleDateString/toLocaleString 로케일 형식. Intl.DateTimeFormat으로 정밀 제어.

1
2
3
4
5
6
7
8
9
const d = new Date();
d.toISOString(); // '2024-01-01T04:00:00.000Z'
d.toLocaleDateString('zh-CN'); // '2024/1/1'
d.toLocaleString('zh-CN', {
year: 'numeric', month: '2-digit', day: '2-digit',
});
// yyyy-MM-dd 직접 조립:
const pad = n => String(n).padStart(2, '0');
const s = `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;

시간대

getTime은 UTC 시간점이고, 로컬 메서드(getHours)는 런타임 시간대를 기준으로 표시됩니다. 저장은 ISO/타임스탬프를 사용합니다.

1
2
3
4
5
6
7
8
9
const d = new Date();
d.getTimezoneOffset(); // 로컬과 UTC 의 오프셋(분)
// UTC 메서드:
d.getUTCHours();
// 지정 타임존으로 포맷:
new Intl.DateTimeFormat('zh-CN', {
timeZone: 'Asia/Shanghai',
}).format(d);
// 서버는 UTC 로 저장、프론트는 로컬 표시

시간 차이

두 Date를 빼면 밀리초 차이가 나옵니다. 초/분/시로 환산합니다. 성능 측정은 performance.now가 더 정밀합니다.

1
2
3
4
5
6
7
8
9
10
const start = Date.now();
doWork();
const ms = Date.now() - start;
// 환산:
const secs = Math.floor(ms / 1000);
const mins = Math.floor(ms / 60000);
// 정밀 측정(밀리초 고정밀):
const t0 = performance.now();
doWork();
const precise = performance.now() - t0;

날짜 연산

일/월을 더할 때는 setDate/setMonth 또는 밀리초 연산을 사용합니다. 월의 0 기반과 월 경계를 주의합니다.

1
2
3
4
5
6
7
8
9
const d = new Date();
d.setDate(d.getDate() + 7); // 7 일 추가
// 월 추가:
d.setMonth(d.getMonth() + 1);
// 시간 추가:
const later = new Date(Date.now() + 3 * 3600_000);
// 시간 부분 정리:
d.setHours(0, 0, 0, 0); // 당일 0 시
// DST 시간대에서는 하루 전체 추가에 setDate 가 안전

날짜 파싱

new Date(string)은 날짜 문자열을 파싱합니다. ISO 형식이 가장 안정적입니다('2024-01-01T12:00:00Z'). 다른 형식은 호환성 차이가 있습니다.

1
2
3
4
5
6
7
8
9
new Date('2024-01-01'); // 로컬 시간대 0 시
new Date('2024-01-01T12:00:00Z'); // UTC 명시
new Date('2024/01/01'); // 브라우저 의존、기대지 말 것
// 해석 검증:
const d = new Date(str);
if (isNaN(d.getTime())) { /* 잘못된 날짜 */ }
// 수동 해석(제어 가능):
const [y, m, day] = '2024-01-01'.split('-').map(Number);
new Date(y, m - 1, day); // month 는 0 기준

Intl 형식화

Intl.NumberFormat은 숫자/통화, Intl.DateTimeFormat은 날짜. 지역에 맞춰 로컬라이즈된 출력을 제공합니다.

1
2
3
4
5
6
7
8
9
10
const n = 1234567.89;
new Intl.NumberFormat('zh-CN').format(n); // '1,234,567.89'
new Intl.NumberFormat('zh-CN', {
style: 'currency', currency: 'CNY',
}).format(n); // '¥1,234,567.89'
new Intl.DateTimeFormat('zh-CN', {
dateStyle: 'medium', timeStyle: 'short',
}).format(new Date());
// 상대 시간:
// new Intl.RelativeTimeFormat('zh-CN').format(-2, 'day')

17.Node 프로세스

process 환경 변수, 매개변수, 종료 코드, 현재 디렉터리와 프로세스 정보.

환경 변수

process.env는 환경 변수를 읽습니다(Node). process.env.NODE_ENV는 일반적입니다. 크로스 플랫폼은 .env 파일로 설정합니다.

1
2
3
4
5
6
7
8
process.env.NODE_ENV; // 'production' | 'development'
process.env.PORT;
// 설정:
process.env.MY_VAR = 'value';
// dotenv 사용:
// import 'dotenv/config';
// .env 파일의 변수를 읽음
// 운영 설정에 비밀 키를 하드코딩하지 않기

명령행 인수

process.argv는 원본 매개변수입니다. 옵션 파싱은 commander/yargs 라이브러리를 사용할 수 있으며, 단순한 시나리오는 직접 작성합니다.

1
2
3
4
5
6
7
8
9
10
11
// $ node app.js --name Nick
const args = process.argv.slice(2);
// commander 사용:
import { Command } from 'commander';
const program = new Command();
program
.option('-n, --name <name>', '名字')
.parse(process.argv);
console.log(program.opts().name);
// 인자 누락 검증:
if (!args.includes('--name')) process.exit(1);

종료 코드

process.exit(code)는 종료하며 반환 코드(0 성공, 0 외 실패)를 가집니다. 잡히지 않은 예외의 기본 코드는 1입니다.

1
2
3
4
5
6
7
8
9
process.exit(0); // 성공
process.exit(1); // 일반 오류
// 미처리 예외의 종료 코드:
process.on('uncaughtException', e => {
console.error(e);
process.exit(1);
});
// async 에서는 process.exitCode 가 더 깔끔:
process.exitCode = 1; // 이벤트 루프를 비운 뒤 종료

현재 디렉터리와 경로

process.cwd() 현재 작업 디렉터리, __dirname 모듈 디렉터리, __filename 모듈 파일(CJS).

1
2
3
4
5
6
7
process.cwd(); // 시작 시점의 cwd
// ESM 에서는:
import { fileURLToPath } from 'node:url';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
// 설정 파일은 cwd 기준인가 모듈 기준인가?
// 실행 디렉토리에 의존하면 process.cwd()
// 스크립트 자체 기준이면 __dirname

프로세스 시그널

SIGINT(Ctrl+C), SIGTERM(kill 기본) 시그널 처리, 우아한 종료(데이터 저장, 연결 해제).

1
2
3
4
5
6
7
8
9
10
process.on('SIGINT', () => {
console.log('收到 Ctrl+C,清理中...');
cleanup();
process.exit(0);
});
process.on('SIGTERM', () => {
console.log('收到终止信号,优雅退出');
server.close();
});
// 중복 발동 방지:플래그로 정리 중 표시

프로세스 정보

process.pid 프로세스 ID, process.uptime() 실행 초, process.memoryUsage() 메모리, process.platform 플랫폼.

1
2
3
4
5
6
7
8
process.pid; // 프로세스 ID
process.platform; // 'win32' | 'linux' | 'darwin'
process.arch; // 'x64'
process.uptime(); // 실행 초
process.memoryUsage(); // { rss, heapUsed, ... }
process.version; // Node 버전
// 자주 쓰는 디버그:
console.log(process.memoryUsage().heapUsed / 1024 / 1024);

표준 스트림

process.stdin/stdout/stderr 세 가지 스트림. 파이프 데이터 처리, 출력 리다이렉트와 파이프 연동.

1
2
3
4
5
6
7
8
9
10
11
// 출력:
process.stdout.write('text'); // 줄바꿈 없음
process.stderr.write('error'); // 오류 스트림
// stdin 을 줄 단위로 읽기(파이프):
import readline from 'node:readline';
const rl = readline.createInterface({ input: process.stdin });
for await (const line of rl) {
console.log(line.toUpperCase()); // 줄마다 처리
}
// $ cat file.txt | node app.js > out.txt
// 스트림이 TTY 인지:process.stdout.isTTY

자식 프로세스 child_process

child_process는 외부 명령을 실행합니다: exec은 출력을 가져오고, spawn은 스트림 방식으로 상호작용하며, fork는 JS 스크립트를 실행합니다. 셸 인젝션에 주의합니다.

1
2
3
4
5
6
7
8
9
10
import { exec, spawn } from 'node:child_process';
// exec:명령 실행 후 전체 출력을 가져옴
exec('ls -la', (err, stdout) => {
if (err) console.error(err.message);
else console.log(stdout);
});
// spawn:대량 출력을 스트림 처리
const child = spawn('node', ['worker.js'], { stdio: 'inherit' });
child.on('exit', code => console.log('退出码', code));
// execFile/spawn 에 배열 인자를 전달하는 것이 우선、셸 인젝션 방지

18.정규 표현식

정규 표현식 리터럴, 매칭, 캡처 그룹, 치환과 일반적인 패턴.

정규 표현식 문법

리터럴 /pattern/과 new RegExp. \d 숫자, \w 단어 문자, \s 공백, [] 문자 클래스, {} 수량자.

1
2
3
4
5
6
7
const email = /^[^@\s]+@[^@\s]+\.[^@\s]+$/;
const phone = /^1[3-9]\d{9}$/;
/\d{4}-\d{2}-\d{2}/; // 날짜
/colou?r/; // color/colour
/\bword\b/; // 단어 경계
// 문자열에서 정규식으로 만들 때는 이중 백슬래시:
new RegExp('\\d+'); // /\d+/ 와 동일

정규 표현식 플래그

g 전역, i 대소문자 무시, m 다중 행(^$가 행과 매칭), s dotAll(.이 줄바꿈과 매칭), u 유니코드.

1
2
3
4
5
6
7
const g = /\b\w+\b/g; // 전체 매칭
const i = /hello/i; // 대소문자 무시
const m = /^start/m; // 각 줄의 시작
const s = /a.b/s; // . 은 줄바꿈에도 매칭
// 인라인:
/(?i)hello/; // 대소문자 무시
// 조합:/ab/gi

test와 match

test는 불리언을 반환하고, match는 매칭 결과를 반환하며, matchAll은 전체를 순회합니다. exec은 단일 매칭에 lastIndex가 있습니다.

1
2
3
4
5
6
7
8
const re = /\d+/g;
re.test('abc123'); // true
'abc123'.match(/\d+/); // ['123', index, input]
// 전체 매칭:
'a1b2'.matchAll(/\d/g);
// [...str.matchAll(/\d/g)].map(m => m[0]);
// 치환 개수:
('a1b2'.match(/\d/g) || []).length; // 2

캡처 그룹

괄호는 캡처, match[1]로 그룹을 가져오고, 명명된 그룹 (?<name>...)은 match.groups.name을 사용합니다. 비캡처 그룹 (?:...).

1
2
3
4
5
6
7
const m = '2024-01-01'.match(/(\d{4})-(\d{2})-(\d{2})/);
m[1]; // '2024'
m[2]; // '01'
// 이름 있는 그룹:
const n = '2024-01-01'.match(/(?<year>\d{4})-(?<month>\d{2})/);
n.groups.year; // '2024'
// 비포착:/(?:ab)+/ 그룹화만 하고 포착하지 않음

정규식 치환

replace는 $1/$<name> 그룹 참조, 콜백 함수를 지원합니다. replaceAll은 전체를 치환합니다(g 의미).

1
2
3
4
5
6
'2024-01-01'.replace(/(\d{4})-(\d{2})-(\d{2})/, '$2/$3/$1');
// 마스킹:
'13812345678'.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
// 콜백:
'abc'.replace(/\w/g, c => c.toUpperCase()); // 'ABC'
// 전체:'a-b'.replaceAll('-', '+')

일반적인 패턴

이메일, URL, IP, 휴대폰 번호, 순수 숫자/문자. 프로덕션 검증은 화이트리스트와 함께 사용하면 더 안정적입니다.

1
2
3
4
5
6
7
8
9
const patterns = {
email: /^[^@\s]+@[^@\s]+\.[^@\s]+$/,
url: /^https?:\/\/[^\s]+$/,
ipv4: /^(\d{1,3}\.){3}\d{1,3}$/,
phone: /^1[3-9]\d{9}$/,
alnum: /^[a-zA-Z0-9]+$/,
};
patterns.email.test('[email protected]'); // true
// 주의:간단한 검증이면 충분、운영은 전용 라이브러리로 엄격하게

정규 표현식 성능

재앙적 백트래킹(중첩 수량자)을 피합니다. 긴 입력은 미리 컴파일된 정규 표현식을 사용합니다. 필요시 문자열 메서드로 대체합니다.

1
2
3
4
5
6
7
8
9
// 느림:중첩 수량자는 백트래킹하기 쉬움
/(a+)+$/; // 'aaaaaaaaX' 에는 느려질 수 있음
// 최적화:
/(?:a+)$/;
// 사전 컴파일 후 재사용:
const re = /pattern/g; // 여러 번 match 재사용
// 단순 판단은 문자열 메서드 우선:
str.includes('x') 优于 /x/;
// 긴 텍스트는 타임아웃/분할 주의

전방탐색과 후방탐색

어서션은 위치만 매칭하고 문자를 소비하지 않습니다: 전방탐색 (?=), 부정 전방탐색 (?!), 후방탐색 (?<=). 비밀번호와 경계 검증에 적합합니다.

1
2
3
4
5
6
7
const re = /foo(?=\d)/; // 숫자가 이어지는 foo
re.test('foo1'); // true
re.test('fooX'); // false
/\d+(?!px)/.test('10em'); // true(10 뒤에 px 가 없음)
/(?<=@)\w+/.exec('[email protected]'); // '@' 뒤의 b 에 매칭
// 비밀번호 강도:영문과 숫자를 모두 포함
/^(?=.*[a-z])(?=.*\d).{6,}$/.test('abc123'); // true

19.빌드와 디버그

npm 스크립트, 번들러, 디버그, 테스트와 코드 스타일.

npm 스크립트

package.json scripts에서 일반적인 명령을 정의합니다. npm run으로 실행. &&로 연결, pre/post 훅.

1
2
3
4
5
6
7
8
9
10
11
12
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"test": "vitest run",
"lint": "eslint . --fix",
"check": "npm run lint && npm run test"
}
}
// $ npm run dev / npm test
// prebuild 는 build 앞에 자동 실행됨

번들러

Vite(현대적 권장), webpack, Rollup, esbuild가 ES 모듈을 브라우저에서 실행 가능한 코드로 번들합니다.

1
2
3
4
5
6
7
8
9
// Vite(esbuild/rollup 기반):
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
build: { outDir: 'dist' },
});
// $ npm create vite@latest my-app
// $ npm run dev # 개발 서버(HMR)
// $ npm run build # 운영 번들

디버그

console 단계, debugger 중단점, Node --inspect 디버거. source map으로 소스 코드 위치 파악.

1
2
3
4
5
6
7
8
9
console.log('info'); // 일반
console.warn('warn'); // 경고
console.error('error'); // 오류
console.debug('debug');
debugger; // 브레이크포인트(DevTools 를 열면 정지)
// Node 디버그:
// $ node --inspect-brk app.js
// 그 후 Chrome 에서 chrome://inspect 를 엶
// 또는 VS Code F5 + launch.json

테스트

Vitest/Jest 단위 테스트: describe 그룹, test/it 케이스, expect 단언. npm test로 실행.

1
2
3
4
5
6
7
8
9
10
11
12
13
import { describe, it, expect } from 'vitest';
function add(a, b) { return a + b; }
describe('add', () => {
it('加和', () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
});
// 비동기 테스트:
it('async', async () => {
await expect(load()).resolves.toBeDefined();
});
// 커버리지:vitest run --coverage

코드 스타일 ESLint

ESLint는 구문과 스타일을 검사하고, Prettier는 형식을 통일합니다. pre-commit 훅으로 자동 검사.

1
2
3
4
5
6
7
8
9
10
11
12
// eslint.config.js
import js from '@eslint/js';
export default [
js.configs.recommended,
{ rules: {
'eqeqeq': ['error', 'always'], // === 강제
'no-unused-vars': 'warn',
}},
];
// $ npx eslint src --fix
// Prettier:$ npx prettier --write .
// husky + lint-staged 로 커밋 전 검사

TypeScript 통합

TS는 타입을 추가합니다. tsc 컴파일, ts-node/tsx로 직접 실행. Vite는 .ts를 기본 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
// $ npm i -D typescript
// tsconfig.json:
{ "compilerOptions": {
"target": "ES2022",
"strict": true,
"module": "ESNext",
"moduleResolution": "bundler"
} }
// $ npx tsc --noEmit # 타입 체크
// Vite 에서는 직접 import './app.ts'
// tsx:$ npx tsx script.ts

CI와 배포

CI는 테스트와 빌드를 자동 실행합니다. GitHub Actions 등 플랫폼 workflow 설정. 산출물을 정적 호스팅/컨테이너에 배포.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# .github/workflows/ci.yml
name: CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
# 배포:dist/ 를 정적 호스팅에 업로드하거나 이미지를 빌드

ESM과 CJS

두 가지 모듈 시스템: ESM 정적 import는 tree-shake 가능, CJS 동적 require는 런타임 로드. type 필드가 기본값을 제어합니다.

1
2
3
4
5
6
7
8
9
// ESM(.mjs 또는 package.json 의 type: module)
import { readFile } from 'node:fs/promises';
export const NAME = 'app';
// CJS(.cjs 또는 type 필드 없음)
const fs = require('node:fs');
module.exports = { NAME: 'app' };
// ESM 은 동적 import 로 CJS 를 로드할 수 있음:
const mod = await import('cjs-pkg');
// 번들 시 tree-shaking 을 위해 ESM 우선

공식 링크

공식 문서와 리소스에 대한 직접 링크.

이 치트시트에 대해

이 페이지는 ECMAScript 2022(JavaScript)의 자체 완결적인 빠른 참조 매뉴얼로, 언어 코어와 브라우저/Node의 일반적인 API가 실제 프로젝트에서 사용되는 약 80%의 일반적인 사용법을 다룹니다. 내용은 현대적 관용구를 지향합니다: let/const 블록 스코프, 화살표 함수, 템플릿 문자열, 구조 분해 할당, 전개 연산자, Promise와 async/await, 옵셔널 체이닝과 null 병합. JavaScript는 Brendan Eich가 1995년에 Netscape에서 발명했으며, 현재 모든 브라우저가 기본적으로 지원하는 유일한 언어로서 웹 프론트엔드, Node.js 서버, 데스크톱 애플리케이션의 기반입니다. 19개 섹션은 각각 하나의 주제에 집중합니다: 기본 문법, 변수와 스코프, 타입, 참조와 값 의미, 제어 흐름, 함수와 클로저, 문자열, 배열과 객체, 메모리와 가비지 컬렉션, 프로토타입과 클래스, 오류 처리, 입출력, 일반적인 함정, 동시성(이벤트 루프), 네트워크(fetch), 시간, 프로세스, 정규 표현식과 빌드 도구. 각 소절에는 「개념 소개 + 바로 복사 가능한 코드 스니펫」이 함께 제공됩니다. 모든 코드와 텍스트는 브라우저에서 로컬로 렌더링되며, 어떤 데이터도 기기를 떠나지 않습니다. 권위 있는 참고 자료는 MDN Web Docs를 보세요.

버전 2.1.0