JavaScript · 5장 자료구조와 자료형
이터러블과 유사 배열: for...of와 펼치기가 되는 조건
어떤 값은 for...of로 돌 수 있고, 어떤 값은 is not iterable 오류가 나요. 어떤 값은 length도 있고 번호로 읽히는데 map을 부르면 is not a function이 나요. 두 오류가 어디서 오는지 알고, 둘 다 진짜 배열로 바꾸는 법을 정리해요.
01. 이터러블: 하나씩 꺼낼 수 있는 값
[Symbol.iterator] 메서드가 있어서 값을 하나씩 꺼내 줄 수 있는 값을 이터러블이라고 해요(원리는 Symbol.iterator에서 봤어요). 배열 · 문자열 · Map · Set이 이터러블이고, 보통 객체는 아니에요.
이터러블이면 for...of만 되는 게 아니라, 값을 하나씩 꺼내 쓰는 문법이 모두 돼요. 펼치기(...), 구조 분해, Array.from, new Set(…) · new Map(…), Promise.all이 다 같은 약속을 써요.
console.log([...'안녕']); // ['안', '녕'] — 문자열 펼치기
console.log(Math.max(...[3, 7, 2])); // 7 — 배열을 인자로 펼치기
const [first, ...rest] = new Set(['a', 'b', 'c']); // Set 구조 분해
console.log(first, rest); // a ['b', 'c']
const user = { name: '하늘' };
console.log([...user]); // 보통 객체를 펼치면?
마지막 줄은 user is not iterable 오류예요. 객체의 키나 값을 돌고 싶으면 Object.keys · Object.entries로 먼저 배열을 만들어요(객체를 배열로). 참고로 { ...user }처럼 중괄호 안에서 펼치는 건 이터러블과 상관없는 객체 복사 문법이라 잘 돼요.
02. 유사 배열: 배열처럼 생겼지만 배열이 아닌 것
length가 있고 0, 1, 2… 번호로 값을 읽을 수 있지만, 배열은 아닌 객체를 유사 배열이라고 해요. 번호로 읽는 건 되는데 map · filter 같은 배열 메서드가 없어요. 이터러블이 아닌 것도 있어요.
const users = { 0: '하늘', 1: '가람', length: 2 }; // 오래된 라이브러리가 주는 모양
console.log(users[0], users.length); // 하늘 2 — 번호로는 읽혀요
console.log(Array.isArray(users)); // false
console.log(users.map(u => u + '님')); // 배열 메서드는?
실무에서 만나는 유사 배열은 주로 브라우저에서 나와요. document.querySelectorAll()이 돌려주는 NodeList, 함수 안의 arguments, 파일 입력의 input.files, 문자열이 그래요. 이 중 NodeList · 문자열처럼 이터러블이기도 한 것은 for...of는 되지만, 배열 메서드가 없거나 일부만 있어요.
03. Array.from: 무엇이든 진짜 배열로
Array.from(값)은 이터러블이든 유사 배열이든 진짜 배열로 바꿔 줘요. 이터러블이라면 [...값]도 같은 일을 하지만, 유사 배열은 이터러블이 아닐 수 있어서 Array.from이 더 넓게 통해요. 두 번째 인자로 함수를 주면 바꾸면서 map까지 해요.
const users = { 0: '하늘', 1: '가람', length: 2 };
console.log(Array.from(users).map(u => u + '님')); // ['하늘님', '가람님']
console.log(Array.from(new Set([1, 1, 2]))); // [1, 2]
// 번호 목록 만들기: length만 있는 유사 배열 + 함수
console.log(Array.from({ length: 5 }, (_, i) => i + 1)); // [1, 2, 3, 4, 5]
console.log(Array.from({ length: 3 }, (_, i) => `${i + 1}월`)); // ['1월', '2월', '3월']
Array.from({ length: n }, 함수)는 페이지 번호, 달력 칸, 별점 다섯 개처럼 개수만 정해진 목록을 만들 때 자주 써요. 함수의 첫 인자는 비어 있는 값(undefined)이라 _로 적고, 두 번째 인자인 번호를 써요.
// 브라우저에서: NodeList를 배열로 바꿔 배열 메서드 쓰기 (이 블록은 실행되지 않아요)
const titles = Array.from(document.querySelectorAll('h2'), h => h.textContent);
목록 화면 아래에 페이지 번호를 그리려고 해요. 전체 글 수 total과 한 페이지 글 수 perPage로 페이지 번호 배열을 만들어 보세요. 지금 값이면 [1, 2, 3, 4, 5]가 나와야 해요. 반복문 없이 써 보세요.
const total = 47;
const perPage = 10;
const pages = []; // 여기를 채워요
console.log(pages);
해답 보기
페이지 수는 Math.ceil(47 / 10) = 5예요(남는 글 7개도 한 페이지가 필요하니 올림). 그 길이의 배열을 Array.from으로 만들면서 번호 + 1을 채워요.
const pages = Array.from({ length: Math.ceil(total / perPage) }, (_, i) => i + 1);
// [1, 2, 3, 4, 5]
04. 실전 포인트
- 언제 쓰나요?
is not iterable·map is not a function오류가 나면 값이 이터러블인지 · 진짜 배열인지 의심해요. NodeList ·input.files·arguments는Array.from으로 바꿔서 배열 메서드를 써요. - 대신 쓰는 것: 이터러블이면
[...값]이 짧아요. 보통 객체는Object.keys·Object.entries로 배열을 만들어요. 함수에서arguments대신 나머지 매개변수(...args)를 쓰면 처음부터 진짜 배열이에요. - 흔한 실수: 보통 객체를
for...of로 돌리는 것, NodeList에map을 부르는 것,Array.from({ length: n })의 함수에서 첫 인자를 번호로 착각하는 것이에요.
정리
- 이터러블은
[Symbol.iterator]가 있는 값이에요. for...of · 펼치기 · 구조 분해 ·new Set()이 모두 받아요. - 유사 배열은
length와 번호는 있지만 배열 메서드가 없는 객체예요. Array.from은 둘 다 진짜 배열로 바꾸고, 두 번째 인자로 바꾸면서 map도 해요.
{ 0: 'a', length: 1 }은 [...obj]와 Array.from(obj) 중 어느 쪽으로 배열이 될까요?
Array.from(obj)만 돼요. 이 객체는 유사 배열이지만 이터러블은 아니라서, 펼치기는 is not iterable 오류가 나요.
Array.from({ length: 3 }, (_, i) => i * 2)의 결과는?
[0, 2, 4]예요. 길이 3인 빈 배열을 만들면서, 각 칸의 번호 i(0, 1, 2)에 2를 곱한 값을 채워요.
Map과 Set은 이터러블일까요, 유사 배열일까요?
이터러블이에요. for...of와 펼치기가 되지만 length와 번호로 읽기는 없어요(크기는 size로 봐요).