⭐️ 노션에서 보기
이터레이션 프로토콜
- 순회 가능한 데이터 컬렉션(자료구조)를 만들기 위해 ECMAScript 사양에 정의하여 미리 약속한 규칙 (ES6 도입)
- 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일
→for…of
문, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용할 수 있도록 일원화
- 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일
이터러블
이터러블 프로토콜을 준수한 객체
Symbol.iterator
를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입을 통해 상속 받은 객체const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function'; // 배열, 문자열, Map, Set 등은 이터러블이다. isIterable([]); // -> true isIterable(''); // -> true isIterable(new Map()); // -> true isIterable(new Set()); // -> true isIterable({}); // -> false
for…of
문, 스프레드 문법, 배열 디스트럭처링 할당 사용 가능const array = [1, 2, 3]; // 배열은 Array.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다. console.log(Symbol.iterator in array); // true // 이터러블인 배열은 for...of 문으로 순회 가능하다. for (const item of array) { console.log(item); // 1 2 3 } // 이터러블인 배열은 스프레드 문법의 대상으로 사용할 수 있다. console.log([...array]); // [1, 2, 3] // 이터러블인 배열은 배열 디스트럭처링 할당의 대상으로 사용할 수 있다. const [a, ...rest] = array; console.log(a, rest); // 1, [2, 3]
일반 객체는 이터러블이 아님
const obj = { a: 1, b: 2 }; // 일반 객체는 Symbol.iterator 메서드를 구현하거나 상속받지 않는다. // 따라서 일반 객체는 이터러블 프로토콜을 준수한 이터러블이 아니다. console.log(Symbol.iterator in obj); // false // 이터러블이 아닌 일반 객체는 for...of 문으로 순회할 수 없다. for (const item of obj) { // -> TypeError: obj is not iterable console.log(item); } // 이터러블이 아닌 일반 객체는 배열 디스트럭처링 할당의 대상으로 사용할 수 없다. const [a, b] = obj; // -> TypeError: obj is not iterable
하지만 TC39 프로세스의 stage 4(Finished) 단계에 제안 되어 있는 스프레드 프로퍼티 제안은 일반 객체에 스프레드 문법의 사용을 허용
const obj = { a: 1, b: 2 }; // 스프레드 프로퍼티 제안(Stage 4)은 객체 리터럴 내부에서 스프레드 문법의 사용을 허용한다. console.log({ ...obj }); // { a: 1, b: 2 }
일반 객체도 이터러블 프로토콜을 준수하도록 구현하면 이터러블이 됨.
이터레이터
이터러블의
Symbol.iterator
메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환반환된 이터레이터는
next
메서드를 가짐// 배열은 이터러블 프로토콜을 준수한 이터러블이다. const array = [1, 2, 3]; // Symbol.iterator 메서드는 이터레이터를 반환한다. const iterator = array[Symbol.iterator](); // Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖는다. console.log('next' in iterator); // true
next
메서드이터러블의 요소를 탐색하기 위한 포인터 역할을 함
호출하면 이터러블을 순회하며 순회 결과를 나타내는 이터레이터 리절트 객체를 반환
value
: 현재 순회 중인 이터러블의 값done
: 이터러블의 순회 완료 여부
// next 메서드를 호출하면 이터러블을 순회하며 순회 결과를 나타내는 이터레이터 리절트 객체를 // 반환한다. 이터레이터 리절트 객체는 value와 done 프로퍼티를 갖는 객체다. console.log(iterator.next()); // { value: 1, done: false } console.log(iterator.next()); // { value: 2, done: false } console.log(iterator.next()); // { value: 3, done: false } console.log(iterator.next()); // { value: undefined, done: true }
빌트인 이터러블
for … of 문
for … of
문 이터러블을 순회하면서 이터러블의 요소를 변수에 할당for (변수선언문 of **이터러블**) { ... }
for (const item of [1, 2, 3]) { // item 변수에 순차적으로 1, 2, 3이 할당된다. console.log(item); // 1 2 3 }
내부적으로 이터레이터의
next
메서드를 호출하여 이터러블을 순회→ next 메서드가 반환한 이터레이터 리절트 객체의
value
값을for … of
문의 변수에 할당→ 이터레이터 리절트 객체의
done
값이false
이면 순회 계속 /true
이면 순회 중단// 이터러블 const iterable = [1, 2, 3]; // 이터러블의 Symbol.iterator 메서드를 호출하여 이터레이터를 생성한다. const iterator = iterable[Symbol.iterator](); for (;;) { // 이터레이터의 next 메서드를 호출하여 이터러블을 순회한다. 이때 next 메서드는 이터레이터 리절트 객체를 반환한다. const res = iterator.next(); // next 메서드가 반환한 이터레이터 리절트 객체의 done 프로퍼티 값이 true이면 이터러블의 순회를 중단한다. if (res.done) break; // 이터레이터 리절트 객체의 value 프로퍼티 값을 item 변수에 할당한다. const item = res.value; console.log(item); // 1 2 3 }
cf.
for … in
문for (변수선언문 in **객체**) { ... }
- 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트
[[Enumerable]]
의 값이true
인 프로퍼티를 순회하며 열거- 프로퍼티 키가 심벌인 프로퍼티는 열거하지 않음
- 프로토타입 체인 상에 존재하는 모든 프로토타입의 프로퍼티 중에서 프로퍼티 어트리뷰트
이터러블과 유사 배열 객체
유사 배열 객체
인덱스로 프로퍼티 값에 접근 가능 →
for
문으로 순회 가능length
프로퍼티를 갖는 객체// 유사 배열 객체 const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 }; // 유사 배열 객체는 length 프로퍼티를 갖기 때문에 for 문으로 순회할 수 있다. for (let i = 0; i < arrayLike.length; i++) { // 유사 배열 객체는 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있다. console.log(arrayLike[i]); // 1 2 3 }
유사 배열 객체는 이터러블이 아닌 일반 객체이기 때문에
Symbol.iterator
메서드가 없으므로for … of
문으로 순회할 수 없음// 유사 배열 객체는 이터러블이 아니기 때문에 for...of 문으로 순회할 수 없다. for (const item of arrayLike) { console.log(item); // 1 2 3 } // -> TypeError: arrayLike is not iterable
단, arguments, NodeList, HTMLCollection은 유사배열이면서 이터러블 (ES6에서 이 객체들에 Symbol.iterator을 구현하면서 이터러블이 됨)
유사 배열 객체 또는 이터러블을 인수로 전달 받아 배열로 변환하여 반환하는
Array.from
메서드를 사용하면 배열로 간단히 변환 가능// 유사 배열 객체 const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 }; // Array.from은 유사 배열 객체 또는 이터러블을 배열로 변환한다 const arr = Array.from(arrayLike); console.log(arr); // [1, 2, 3]
이터레이션 프로토콜의 필요성
- 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일하여 for…of 문, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용할 수 있도록 일원화 (ES6~)
- 하나의 순회 방식을 갖도록 규정하여 효율성 up
- 데이터 소비자와 공급자를 연결하는 인터페이스의 역할
- for…of 문, 스프레드 문법, 배열 디스트럭처링 할당 → 데이터 소비자
- 이터러블 → 데이터 공급자
사용자 정의 이터러블
사용자 정의 이터러블 구현
- 일반 객체도 이터레이션 프로토콜을 준수하도록 구현하면 사용자 정의 이터러블이 됨
// 피보나치 수열을 구현한 사용자 정의 이터러블
const fibonacci = {
// Symbol.iterator 메서드를 구현하여 이터러블 프로토콜을 준수한다.
[Symbol.iterator]() {
let [pre, cur] = [0, 1]; // "36.1. 배열 디스트럭처링 할당" 참고
const max = 10; // 수열의 최대값
// Symbol.iterator 메서드는 next 메서드를 소유한 이터레이터를 반환해야 하고
// next 메서드는 이터레이터 리절트 객체를 반환해야 한다.
return {
next() {
[pre, cur] = [cur, pre + cur]; // "36.1. 배열 디스트럭처링 할당" 참고
// 이터레이터 리절트 객체를 반환한다.
return { value: cur, done: cur >= max };
}
};
}
};
// 이터러블인 fibonacci 객체를 순회할 때마다 next 메서드가 호출된다.
for (const num of fibonacci) {
console.log(num); // 1 2 3 5 8
}
- 이터레이션 프로토콜을 준수하도록 Symbol.Iterator 메서드 구현
- Symbol.Iterator 메서드가 next 메서드를 갖는 이터레이터를 반환하도록 함
- 이터레이터의 next 메서드는 done과 value 프로퍼티를 가지는 이터레이터 리절트 객체를 반환하도록 함
// 이터러블은 스프레드 문법의 대상이 될 수 있다.
const arr = [...fibonacci];
console.log(arr); // [ 1, 2, 3, 5, 8 ]
// 이터러블은 배열 디스트럭처링 할당의 대상이 될 수 있다.
const [first, second, ...rest] = fibonacci;
console.log(first, second, rest); // 1 2 [ 3, 5, 8 ]
이터러블을 생성하는 함수
-
- 이터러블 내부에 수열의 최대값을 가지고 있음
TO-BE
수열의 최대값을 외부에서 전달할 수 있도록 수정
// 피보나치 수열을 구현한 사용자 정의 이터러블을 반환하는 함수. 수열의 최대값을 인수로 전달받는다. const fibonacciFunc = function (max) { let [pre, cur] = [0, 1]; // **Symbol.iterator 메서드를 구현한 이터러블을 반환**한다. return { [Symbol.iterator]() { return { next() { [pre, cur] = [cur, pre + cur]; return { value: cur, done: cur >= max }; } }; } }; }; // 이터러블을 반환하는 함수에 수열의 최대값을 인수로 전달하면서 호출한다. for (const num of fibonacciFunc(10)) { console.log(num); // 1 2 3 5 8 }
이터러블이면서 이터레이터인 객체를 생성하는 함수
-
이터레이터를 생성하려면 이터러블의 Symbol.iterator 메서드를 호출해야 함
// fibonacciFunc 함수는 이터러블을 반환한다. const iterable = fibonacciFunc(5); // 이터러블의 Symbol.iterator 메서드는 이터레이터를 반환한다. const iterator = iterable[Symbol.iterator](); console.log(iterator.next()); // { value: 1, done: false } console.log(iterator.next()); // { value: 2, done: false } console.log(iterator.next()); // { value: 3, done: false } console.log(iterator.next()); // { value: 5, done: true }
TO-BE
이터러블이면서 이터레이터인 객체를 생성하면
Symbol.iterator
메서드를 호출하지 않아도 됨// 이터러블이면서 이터레이터인 객체. 이터레이터를 반환하는 Symbol.iterator 메서드와 // 이터레이션 리절트 객체를 반환하는 next 메서드를 소유한다. { [Symbol.iterator]() { return **this**; }, next() { return { value: any, done: boolean }; } }
fibonacciFunc
함수에 적용// 이터러블이면서 이터레이터인 객체를 반환하는 함수 const fibonacciFunc = function (max) { let [pre, cur] = [0, 1]; // Symbol.iterator 메서드와 next 메서드를 소유한 이터러블이면서 이터레이터인 객체를 반환 return { [Symbol.iterator]() { return this; }, // next 메서드는 이터레이터 리절트 객체를 반환 next() { [pre, cur] = [cur, pre + cur]; return { value: cur, done: cur >= max }; } }; }; // iter는 이터러블이면서 이터레이터다. let iter = fibonacciFunc(10); // iter는 이터러블이므로 for...of 문으로 순회할 수 있다. for (const num of iter) { console.log(num); // 1 2 3 5 8 } // iter는 이터러블이면서 이터레이터다 iter = fibonacciFunc(10); // iter는 이터레이터이므로 이터레이션 리절트 객체를 반환하는 next 메서드를 소유한다. console.log(iter.next()); // { value: 1, done: false } console.log(iter.next()); // { value: 2, done: false } console.log(iter.next()); // { value: 3, done: false } console.log(iter.next()); // { value: 5, done: false } console.log(iter.next()); // { value: 8, done: false } console.log(iter.next()); // { value: 13, done: true }
무한 이터러블과 지연 평가
// 무한 이터러블을 생성하는 함수
const fibonacciFunc = function () {
let [pre, cur] = [0, 1];
return {
[Symbol.iterator]() { return this; },
next() {
[pre, cur] = [cur, pre + cur];
// **무한을 구현해야 하므로 done 프로퍼티를 생략한다.**
return { value: cur };
}
};
};
// fibonacciFunc 함수는 무한 이터러블을 생성한다.
for (const num of fibonacciFunc()) {
if (num > 10000) break;
console.log(num); // 1 2 3 5 8...4181 6765
}
// 배열 디스트럭처링 할당을 통해 무한 이터러블에서 3개의 요소만 취득한다.
const [f1, f2, f3] = fibonacciFunc();
console.log(f1, f2, f3); // 1 2 3
- 배열이나 문자열 등은 모든 데이터를 메모리에 미리 확보한 다음 데이터를 공급하지만, 위 예제의 이터러블은 지연 평가를 통해 데이터 생성
- 지연 평가(lazy evalutaion): 데이터가 필요한 시점 이전까지는 미리 데이터를 생성하지 않다가 데이터가 필요한 시점이 되면 그때야 비로소 데이터를 생성하는 기법
- 빠른 실행 속도
- 불필요한 메모리 소비 X
- 무한 표현 가능
- 지연 평가(lazy evalutaion): 데이터가 필요한 시점 이전까지는 미리 데이터를 생성하지 않다가 데이터가 필요한 시점이 되면 그때야 비로소 데이터를 생성하는 기법
공부 내용 복기
'개발서적 > 모던 자바스크립트 Deep Dive' 카테고리의 다른 글
[모던 자바스크립트 Deep Dive] 38장: 브라우저의 렌더링 과정 (0) | 2024.12.05 |
---|---|
[모던 자바스크립트 Deep Dive] 37장: Set과 Map (0) | 2024.12.05 |
[모던 자바스크립트 Deep Dive] 36장: 디스트럭처링 할당 (0) | 2024.12.05 |
[모던 자바스크립트 Deep Dive] 35장: 스프레드 문법 (0) | 2024.12.05 |
[모던 자바스크립트 Deep Dive] 33장: 7번째 데이터 타입 Symbol (0) | 2024.12.05 |