이터러블과 스프레드
- 10.1 이터레이션 프로토콜
- 10.2 빌트인 이터러블
- 10.3 for...of 문
- 10.4 사용자 정의 이터러블과 지연 평가
- 10.5 스프레드 문법
- 10.6 디스트럭처링 할당
- 10.7 Set과 Map
for...of, 스프레드, 디스트럭처링이 배열뿐 아니라 Map, Set, 문자열에서도 되는 이유 -- 그 답이 바로 이터레이션 프로토콜이야. 여기에 스프레드, 디스트럭처링, Set/Map까지 묶어서 정리해볼게.
두 가지 프로토콜이 있어. 이터러블 프로토콜은 Symbol.iterator 메서드를 가진 객체를 말하고, 이터레이터 프로토콜은 그 Symbol.iterator가 반환하는 객체인데, next() 메서드를 가지고 있어서 { value, done } 형태의 이터레이터 리절트 객체를 반환하지.
const arr = [1, 2, 3];
const iterator = arr[Symbol.iterator]();
iterator.next(); // { value: 1, done: false }
iterator.next(); // { value: 2, done: false }
iterator.next(); // { value: 3, done: false }
iterator.next(); // { value: undefined, done: true }
빌트인 이터러블은 꽤 많아. Array, String, Map, Set, TypedArray, arguments, NodeList, HTMLCollection 등이 다 이터러블이거든. for...in이랑 for...of를 헷갈리면 안 되는데, for...in은 객체의 열거 가능한 프로퍼티 키를 순회하고 프로토타입 체인까지 올라가고, for...of는 이터러블의 이터레이터가 반환하는 value를 순회해.
이터러블의 진짜 멋진 점은 **지연 평가(Lazy Evaluation)**야. next()가 호출될 때마다 값을 하나씩 생성해서 무한 이터러블도 구현할 수 있어.
function fibonacci() {
let [pre, cur] = [0, 1];
return {
[Symbol.iterator]() { return this; },
next() {
[pre, cur] = [cur, pre + cur];
return { value: cur, done: false }; // 무한
}
};
}
이터러블 위에서 동작하는 대표적인 문법이 스프레드야. ... 세 개의 점이 하는 일이 두 가지인데, 스프레드 문법은 이터러블을 개별 요소로 펼치는 거고, Rest 파라미터는 반대로 모으는 거지.
// 배열 결합/복사
const merged = [...arr1, ...arr2];
const copy = [...original]; // 얕은 복사
// 객체에서도 사용 (ES2018)
const merged = { ...obj1, ...obj2 };
const changed = { ...obj, x: 100 };
배열/객체의 복사, 병합, 함수 인수 전달이 concat, slice, Object.assign을 대체할 만큼 간결해졌지. 단, 얕은 복사라는 점은 잊지 말자.
디스트럭처링 할당은 배열이나 객체를 분해해서 변수에 바로 할당하는 문법이야. 배열은 인덱스 기준으로 순서대로, 객체는 프로퍼티 키 기준이라 순서가 상관없어.
const [a, b, ...rest] = [1, 2, 3, 4]; // rest = [3, 4]
const { name, age } = { name: 'Lee', age: 20 };
const { x, ...rest } = { x: 1, y: 2, z: 3 }; // rest = { y: 2, z: 3 }
실무에서 가장 많이 쓰는 패턴은 함수 매개변수에서 바로 분해하는 거야. React의 useState, props 분해 등 매일 쓰는 문법이지.
function printTodo({ content, completed }) {
console.log(`${content}: ${completed ? '완료' : '미완료'}`);
}
마지막으로 Set과 Map이야. Set은 중복되지 않는 값들의 집합인데, [...new Set(arr)]로 배열 중복 제거하는 건 실무에서 정말 자주 쓰는 패턴이야. 스프레드랑 조합하면 합집합, 교집합, 차집합도 간단하지.
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
new Set([...a, ...b]); // 합집합 {1, 2, 3, 4}
new Set([...a].filter(x => b.has(x))); // 교집합 {2, 3}
new Set([...a].filter(x => !b.has(x))); // 차집합 {1}
Map은 키-값 쌍의 컬렉션인데, 일반 객체와 달리 모든 값을 키로 사용할 수 있어. 객체도, 함수도 키가 돼. 키 타입이 다양하거나, 요소를 빈번하게 추가/삭제하거나, size로 개수를 바로 알고 싶으면 Map이 더 적합해. 둘 다 이터러블이라 for...of나 스프레드도 쓸 수 있고.
정리
10장 읽고 기억할 거 세 가지:
- 이터레이션 프로토콜은 다양한 데이터 소스를 통일된 방식으로 순회하게 해주는 인터페이스야.
for...of, 스프레드, 디스트럭처링 전부 이 프로토콜 위에서 동작하지. - 스프레드는 펼치고, Rest는 모아. 같은
...인데 위치에 따라 역할이 반대야. 배열/객체 복사와 병합이 훨씬 간결해졌지만 얕은 복사라는 점은 기억하자. - Set은 중복 제거와 집합 연산, Map은 모든 값을 키로 쓸 수 있는 컬렉션이야. 둘 다 이터러블이라 스프레드와
for...of가 먹혀.