매년 진화하는 JavaScript, 2025년에는 무엇이 달라졌나
TC39(ECMAScript 기술 위원회)는 매년 6월 새로운 ECMAScript 사양을 확정합니다. 2015년의 ES6(ES2015)가 JavaScript의 대격변이었다면, 이후의 업데이트는 매년 실용적인 기능을 조금씩 추가하는 점진적 개선 방식을 따르고 있습니다.
ES2025에서는 오랫동안 기다려온 기능들이 다수 확정되었는데, 특히 Set 메서드, Iterator 헬퍼, Temporal API 등은 일상적인 코딩 패턴을 크게 바꿀 잠재력을 가지고 있습니다. 하나씩 살펴봅니다.
---
Set 메서드 — 드디어 집합 연산이 내장되다
JavaScript의 `Set`은 ES6에서 도입되었지만, 합집합·교집합·차집합 같은 기본적인 집합 연산이 빠져 있어 실무에서 아쉬운 부분이 많았습니다. ES2025에서 7개의 새로운 메서드가 추가되었습니다.
```javascript const frontend = new Set(['React', 'Vue', 'Angular', 'Svelte']); const popular = new Set(['React', 'Vue', 'Next.js', 'Node.js']);
// 교집합 — 공통 요소 frontend.intersection(popular); // Set {'React', 'Vue'}
// 합집합 — 모든 요소 frontend.union(popular); // Set {'React', 'Vue', 'Angular', 'Svelte', 'Next.js', 'Node.js'}
// 차집합 — frontend에만 있는 요소 frontend.difference(popular); // Set {'Angular', 'Svelte'}
// 대칭 차집합 — 한쪽에만 있는 요소 frontend.symmetricDifference(popular); // Set {'Angular', 'Svelte', 'Next.js', 'Node.js'}
// 부분집합 여부 new Set(['React', 'Vue']).isSubsetOf(frontend); // true
// 상위집합 여부 frontend.isSupersetOf(new Set(['React'])); // true
// 교집합이 없는지 (서로소) frontend.isDisjointFrom(new Set(['Django', 'Flask'])); // true ```
기존에는 이런 연산을 위해 배열로 변환하고 `filter`를 사용하거나 lodash의 `_.intersection`에 의존해야 했는데, 이제 네이티브로 해결할 수 있게 된 것이죠. 특히 태그 필터링, 권한 비교, 데이터 비교 같은 패턴에서 유용합니다.
---
Iterator 헬퍼 — 체이닝의 귀환
배열에는 `map`, `filter`, `reduce` 같은 메서드가 있지만, Iterator에는 없었습니다. `Map`, `Set`, 제너레이터 등에서 반환하는 Iterator를 변환하려면 매번 배열로 변환(`Array.from`)해야 했죠. ES2025에서 Iterator 프로토타입에 헬퍼 메서드가 추가되었습니다.
```javascript // Map의 entries를 직접 체이닝 const userMap = new Map([ ['alice', { age: 28, role: 'admin' }], ['bob', { age: 35, role: 'user' }], ['charlie', { age: 22, role: 'admin' }], ]);
const adminNames = userMap.entries() .filter(([_, user]) => user.role === 'admin') .map(([name, _]) => name.toUpperCase()) .toArray(); // ['ALICE', 'CHARLIE'] ```
```javascript // 제너레이터와 함께 사용 — 무한 시퀀스 처리 function* fibonacci() { let [a, b] = [0, 1]; while (true) { yield a; [a, b] = [b, a + b]; } }
const first10 = fibonacci() .take(10) // 처음 10개만 .toArray(); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
const evenFibs = fibonacci() .filter(n => n % 2 === 0) .take(5) .toArray(); // [0, 2, 8, 34, 144] ```
Iterator 헬퍼는 지연 평가(lazy evaluation)로 동작합니다. 배열 메서드는 각 단계마다 새 배열을 생성하지만, Iterator 헬퍼는 `toArray()`나 `for...of`로 실제 소비할 때만 값을 계산합니다. 대량의 데이터를 다룰 때 메모리 효율이 훨씬 좋아지는 것이죠.
---
Promise.withResolvers — 외부에서 제어하는 Promise
Promise를 생성할 때 `resolve`와 `reject`를 외부 변수에 저장하는 패턴이 있습니다. 이벤트 핸들러나 타이머와 연동할 때 흔히 사용되는데, 기존에는 다소 어색한 코드가 되었죠.
```javascript // 기존 — 변수 호이스팅 필요 let resolve, reject; const promise = new Promise((res, rej) => { resolve = res; reject = rej; });
// ES2025 — 깔끔한 구조 분해 const { promise, resolve, reject } = Promise.withResolvers();
// 실전 활용: WebSocket 메시지 대기 function waitForMessage(ws, type) { const { promise, resolve, reject } = Promise.withResolvers(); const timeout = setTimeout(() => reject(new Error('Timeout')), 5000);
ws.addEventListener('message', function handler(event) { const data = JSON.parse(event.data); if (data.type === type) { clearTimeout(timeout); ws.removeEventListener('message', handler); resolve(data); } });
return promise; }
const response = await waitForMessage(socket, 'AUTH_RESULT'); ```
간단하지만, 이 패턴이 자주 필요한 프로젝트(WebSocket, Worker 통신, 사용자 입력 대기 등)에서는 코드 가독성에 상당한 차이를 만들어 냅니다.
---
Temporal API — Date를 대체하는 새로운 날짜/시간 API
JavaScript의 `Date` 객체는 1995년 Java의 `java.util.Date`를 급히 베낀 것으로 유명합니다. 타임존 처리가 엉망이고, 월이 0부터 시작하며, 뮤터블해서 버그 유발 가능성이 높죠. Moment.js, date-fns, Day.js 같은 라이브러리가 등장한 이유가 바로 이것입니다.
`Temporal`은 이 모든 문제를 해결하기 위해 설계된 새로운 API입니다. Stage 3에서 오랜 시간을 보냈지만, 브라우저 구현이 진행되고 있어 머지않아 사용할 수 있을 것으로 보입니다.
```javascript // Temporal.PlainDate — 타임존 없는 날짜 const date = Temporal.PlainDate.from('2025-07-15'); const nextWeek = date.add({ days: 7 }); // 2025-07-22
// Temporal.ZonedDateTime — 타임존 포함 const meeting = Temporal.ZonedDateTime.from({ timeZone: 'Asia/Seoul', year: 2025, month: 7, day: 15, hour: 14, minute: 30, });
// 다른 타임존으로 변환 const nyTime = meeting.withTimeZone('America/New_York'); console.log(nyTime.toString()); // 2025-07-15T01:30:00-04:00[America/New_York]
// 두 날짜 사이의 차이 const start = Temporal.PlainDate.from('2025-01-01'); const end = Temporal.PlainDate.from('2025-07-15'); const duration = start.until(end); console.log(duration.toString()); // P195D (195일) console.log(duration.total('months')); // 약 6.4 ```
`Temporal`의 모든 객체는 불변(immutable)입니다. `add`, `subtract`, `with` 같은 메서드는 항상 새 객체를 반환하므로, `Date`에서 흔했던 의도치 않은 뮤테이션 버그가 원천적으로 차단됩니다.
---
정규식 v 플래그와 기타 기능들
RegExp v 플래그 — 유니코드 집합 표기법
```javascript // 기존 u 플래그 const emojiRegex = /\p{Emoji}/u;
// v 플래그 — 집합 연산 가능 const nonEmojiLetter = /[\p{Letter}--\p{Emoji}]/v;
// 문자열 속성 const koreanOnly = /[\p{Script=Hangul}]/v; koreanOnly.test('가'); // true koreanOnly.test('A'); // false
// 교차 (&&) const latinDigit = /[\p{Script=Latin}&&\p{Number}]/v; ```
Object.groupBy — 그룹핑 유틸리티
```javascript const products = [ { name: '노트북', category: '전자기기', price: 1500000 }, { name: '마우스', category: '전자기기', price: 50000 }, { name: '책상', category: '가구', price: 300000 }, { name: '의자', category: '가구', price: 450000 }, ];
const grouped = Object.groupBy(products, p => p.category); // { // '전자기기': [{ name: '노트북', ... }, { name: '마우스', ... }], // '가구': [{ name: '책상', ... }, { name: '의자', ... }] // }
// 가격대별 그룹핑 const byPrice = Object.groupBy(products, p => p.price >= 500000 ? '고가' : p.price >= 200000 ? '중가' : '저가' ); ```
`Array.prototype.reduce`로 그룹핑하던 장황한 패턴이 한 줄로 줄어듭니다.
---
브라우저 호환성과 실무 적용 전략
ES2025 기능의 브라우저 지원 상황은 기능마다 다릅니다. Set 메서드, Object.groupBy, Promise.withResolvers는 이미 주요 브라우저에서 지원되고 있어 프로덕션 적용이 가능합니다. Iterator 헬퍼는 Chrome/Edge에서 지원이 시작되었고, Temporal API는 아직 폴리필이 필요한 상태입니다.
TypeScript를 사용 중이라면 `tsconfig.json`의 `lib`에 `"ESNext"`를 포함하거나, 특정 기능의 타입 정의를 추가해야 합니다. 빌드 도구(Babel, esbuild, SWC)의 타겟 설정도 함께 확인하세요.
JavaScript는 "완성된 언어"가 아니라 "계속 진화하는 언어"입니다. 매년 추가되는 기능을 모두 즉시 도입할 필요는 없지만, Set 메서드나 Object.groupBy처럼 즉시 활용 가능한 기능부터 프로젝트에 적용해보면 코드의 표현력과 가독성이 눈에 띄게 향상될 것입니다.