Tistory
모든 것을 배열로 바꾸는 것을 그만두세요 (대신 일을 덜 하세요)
Array.prototype.map/filter/slice 체인은 중간 배열 을 쌓는다. Iterator Helpers(values().filter().take()) 는 지연 평가로 중간 할당을 없애고 take(n) 에서 즉시 종료한다.
핵심 요약
Iterator Helpers(Iterator.prototype.*) 는 ES2025 에서 표준화되어 이터레이터 객체 에 체인 메서드를 얹는다. array.values(), keys(), entries() 또는 제너레이터로 이터레이터를 얻은 뒤 .filter().map().take(n).toArray() 처럼 쓴다. 중간 배열이 없고(메모리), take(n) 에서 n 개를 만난 순간 이후 원소는 아예 평가되지 않는다(시간). 비동기 이터러블(for await) 도 동일 API 를 제공해 페이지네이션 API 처리가 자연스럽다. 한계는 (1) reduce 는 즉시 소모 — 게으름의 이득 없음, (2) 랜덤 접근(items[5]) 불가, (3) 일회용 — 재순회 시 재생성 필요, (4) console.log 로 이터레이터를 건드리면 상태가 변한다. 실무 규칙: 전체 배열이 필요 없다면 배열을 만들지 않는다.
데이터 파이프라인을 '즉시 평가(eager) vs 지연 평가(lazy)' 라는 단일 축으로 본다. 배열 메서드 체인은 각 단계마다 새 배열 할당 + 전체 순회, Iterator Helpers 는 필요한 만큼만 흐른다. 10,000개 중 상위 10개만 쓸 때 차이가 극명.
JS 면접에서 .map().filter().slice() 를 쓰면 '가독성 좋은 코드' 로 합격권이었다. 이제 질문이 깊어진다: '10,000건 중 10건만 필요할 때도 이 체인이 최선이냐?' Iterator Helpers 는 Node 22+/모던 브라우저에서 표준이며, 면접 답변에 '상황 별 판단 기준' 을 더할 수 있는 소재.
학습 포인트
면접 답변으로 연결할 학습 포인트입니다.
중간 할당 제거 = 메모리 이득
// 기존 — 3개 중간 배열 할당
const result = items
.filter(isVisible) // 새 배열 1
.map(transform) // 새 배열 2
.slice(0, 10); // 새 배열 3
// Iterator Helpers — 중간 배열 0
const result = items.values()
.filter(isVisible)
.map(transform)
.take(10)
.toArray();
10,000개 배열에서 10개만 필요할 때, 전자는 10,000 + 9,000 + 10 = 19,010 원소의 임시 메모리. 후자는 0. GC 부담, 가상화 리스트 스크롤 FPS, Node 메모리 사용량이 전부 개선.
slice(0, 10) 가 배열 전체를 복사하지 않는다고 착각하는 것. 실제로는 새 배열 생성 후 10개 복사.
take(n) 조기 종료
Iterator Helpers 의 진짜 이득은 시간 이다. filter 가 true 를 10번 반환한 순간 take(10) 이 종료 신호를 보내 뒤 원소는 평가되지 않는다.
function* expensive(data) {
for (const x of data) {
console.log('evaluating', x.id); // 무거운 계산
yield compute(x);
}
}
const first10 = expensive(data).take(10).toArray();
// data 가 10,000 이어도 'evaluating' 은 10번만 출력(필터 조건 충족 가정)
compute 가 O(N) 이고 총 원소가 10,000 이면 시간 절감 비율이 1000배 까지 나올 수 있다.
array.slice(0,10).map(expensive) 는 조기 종료지만, array.map(expensive).slice(0,10) 은 전부 계산 후 자르기 — 순서가 핵심.
적합 vs 부적합 — 판단 기준
| 상황 | 적합 | 이유 |
|---|---|---|
| 상위 N 개만 | O | take(n) 조기 종료 |
| 무한/스트리밍 데이터 | O | 버퍼링 없음 |
| 체인이 3단계+ | O | 중간 배열 누적 제거 |
| 전체 순회/집계 | X | reduce 는 어차피 전수 |
랜덤 접근(items[5]) | X | 지원 안 함 |
| 재사용(두 번 순회) | X | 일회용 — 재생성 필요 |
판단 룰: 'N 개 중 K 개(K ≪ N)만 쓸 때 O, 전부 쓸 때 X'.
'지연 평가가 항상 빠르다' 는 오해 — 전수 순회에서는 오히려 오버헤드(함수 호출) 가 크다.
읽는 순서
- 1이론
MDN
Iterator.prototype및AsyncIterator.prototype메서드 목록(map/filter/take/drop/toArray) 각각을 1줄 요약. - 2구현
10,000 원소 배열에서
.filter().map().slice(0,10)과.values().filter().map().take(10).toArray()를 벤치마크(performance.now())로 비교. - 3실무
본인 프로젝트의 '무한 스크롤' 또는 '상위 N 랭킹' 로직을 Iterator Helpers 로 이전 PR 을 준비하고 FPS/메모리 개선 수치 기록.
- 4설명
'이터레이터 헬퍼가 적합한 5 가지 패턴 / 부적합한 3 가지 패턴' 을 팀 JS 스터디에서 공유.
면접 연결 질문
[좋은 답변] 공간: 전자 O(N) × 3, 후자 O(10). 시간: filter 통과 원소가 상위에 몰려 있으면 후자 O(K), 밀려 있으면 전자와 유사. 결론 — filter selectivity 가 낮고 N 이 클수록 후자가 유리.
[좋은 답변] async function* fetchPages() 로 페이지 yield, .filter(isValid).take(10).toArray(). 전체 페이지를 메모리에 버퍼링하지 않고 조건 충족 10개 만 받자마자 요청 중단. 커서/페이지 관리가 런타임에 내장됨.
[좋은 답변] (1) 같은 컬렉션을 여러 번 순회하는 UI 로직 — 일회용 이슈, 배열이 맞음. (2) 랜덤 인덱스 접근이 필요한 테이블 — 배열이 맞음. (3) 작은 N (<100) — 오버헤드가 이득보다 큼.
자기 점검
모두 기계적으로 치환 가능하다는 오해 — 재순회 하는 코드는 불가.
로그는 읽기 전용이라 안전하다는 오해 — 배열 복사 후 로그해야 안전.