JavaScript & Node.js
[Iteration, Iterable, Iterator]이란 무엇인가
Haksae
2022. 2. 9. 14:31
1. Iteration Protocol
이터레이션 프로토콜이란 순회 가능한 자료 구조를 만들기 위해서 ES6부터 도입된 규약이다.
이는 객체 자료구조와 무관한 컬렉션 탐색 기능을 제공하기 위해 지켜야하는 구현 규약이라고 할 수 있다.
- ES6 이전에는 배열, 문자열, DOM 콜렉션 등이 각자 방법으로 데이터를 순회할 수 있도록 구성되어 있엇지만, ES6에 들어서면서 이러한 순회 가능한 데이터들이 이터레이션 프로토콜을 준수하여 동일하게 동작하게 설계했다.
- 이터레이션 프로토콜에는 1) Iterable Protocol, 2) Iterator Protocol, 2가지가 있다.
1.1 Iterable
- Iterable Protocol
1. 오브젝트가 반복할 수 있는 구조여야한다. (ex: array, map, set)
2. Symbol.Iterator를 가지고 있어야한다.
*기본적으로 Symbol.iterator를 가지고 있는 오브젝트는 Array, Argument, String, TypeArray, Map, Set, DOM NodeList)
- 위의 조건을 만족한다면, Symbol.iterator를 호출하면 이터레이터를 반환할 것이다.
- Iterable
즉 1) Iterable은 순회 가능한 자료구조 오브젝트면서
Iterator를 리턴하는 [Symbol.iterator]() 를 가진 값이라고 할 수 있다.
- 1.2 Iterator
- Iterator Protocol
1. next 메소드롤 소유해야한다.
2. next 메소드를 호출하면, 이터러블을 순회하며 value, done 프로퍼티를 갖는 iterator result 객체를 반환해야한다.
- Iterator
이터러블 오브젝트의 Symbol.iterator을 호출하여 반환받은 오브젝트를 뜻한다.
{value, done} 객체를 리턴하는 next() 를 가진 값
// 배열은 이터러블 프로토콜을 준수한 이터러블이다.
const array = [1, 2, 3];
// Symbol.iterator 메소드는 이터레이터를 반환한다.
const iterator = array[Symbol.iterator]();
// 이터레이터 프로토콜을 준수한 이터레이터는 next 메소드를 갖는다.
console.log('next' in iterator); // true
// 이터레이터의 next 메소드를 호출하면 value, done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환한다.
// next 메소드를 호출할 때 마다 이터러블을 순회하며 이터레이터 리절트 객체를 반환한다.
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}
- 이터러블 프로토콜을 준수한 이터러블은 Symbol.iterator 메소드를 소유한다.
- 이 메소드를 호출하면 이터레이터를 반환한다.
- 이터레이터 프로토콜을 준수한 이러테이터는 next 메소드를 갖는다.
- 이터레이터의 next 메소드를 호출하면 value, done 프로터티를 갖는 이터레이터 리절트 객체를 반환한다
- 이터레이터의 next 메소드는 이터러블의 각 요소를 순회하기 위한 포인터의 역할을 한다.
- next 메소드를 호출하면 이터러블을 순차적으로 한 단게씩 순회하며 이터레이터 리절트 객체를 반환한다.
- 이터레이터의 next 메소드가 반환하는 이터레이터 리절트 객체의 value 프로퍼티는 현재 순회 중인 이터러블의 값을 반환하고 done 프로퍼티는 이터러블의 순회의 완료 여부를 반환한다.
1.3 Built-in Iterable
다음의 표준 빌트인 객체들은 빌트인 이터러블이다.
빌트인 이터러블 | Symbol.iterator 메서드 |
Array | Array.prototype[Symbol.iterator] |
String | String.prototype[Symbol.iterator] |
Map | Map.prototype[Symbol.iterator] |
Set | Set.prototype[Symbol.iterator] |
TypedArray | TypedArray.prototype[Symbol.iterator] |
arguments | arguments[Symbol.iterator] |
DOM 컬렉션 | NodeList.prototype[Symbol.iterator] HTMLCollection.prototype[Symbol.iterator |
2. Iteration Protocol의 필요성
- 이터러블은 데이터 소비자(Data Consumer)인 for ... of, spread 문법 등의 테이터 공급자(Data Provider)의 역할을 한다.
- 만약 다양한 데이터 소스가 각자의 순회 방식을 갖는다면 데이터 소비자는 다양한 데이터 소스의 순회 방식을 모두 지원해야한다. (이러한 방식은 효율적이지 않다)
- 때문에 다양한 데이터 소스가 이터레이션 프로토콜을 준수하게하여, 규정된 이터레이션 프로토콜만을 지원하도록 구현하게하여 효율을 높이는 것이 이터레이션 프로토콜의 목적이다.
- 즉 이터레이션 프로토콜은 다양한 데이터 소스가 하나의 순회 방식을 갖도록 규정하여 데이터 소비자가 효율적으로 다양한 데이터 소스를 사용할 수 있도록 데이터 소비자와 데이터 소스를 연결하는 인터페이스 역할을 한다.
3. Custom Iterable
- 일반 객체는 Symbol.iterator 메소드를 소유하지 않기 때문에 이터러블이 아니다. 떄문에 일반 객체는 for of 문과 같은 방식으로 순회할 수 없다.
- 하지만 일반 객체가 이터레이션 프로토콜을 준수하도록 구현하면 이터러블이 된다.
- 이 경우 for of, spread, destructuring 등이 가능하다.
- 예시
const fibonacci = {
// Symbol.iterator 메소드를 구현하여 이터러블 프로토콜을 준수
[Symbol.iterator]() {
let [pre, cur] = [0, 1];
// 최대값
const max = 10;
// Symbol.iterator 메소드는 next 메소드를 소유한 이터레이터를 반환해야 한다.
// next 메소드는 이터레이터 리절트 객체를 반환
return {
// fibonacci 객체를 순회할 때마다 next 메소드가 호출된다.
next() {
[pre, cur] = [cur, pre + cur];
return {
value: cur,
done: cur >= max
};
}
};
}
};
// 이터러블의 최대값을 외부에서 전달할 수 없다.
for (const num of fibonacci) {
// for...of 내부에서 break는 가능하다.
// if (num >= 10) break;
console.log(num); // 1 2 3 5 8
}
// spread 문법과 디스트럭처링을 사용하면 이터러블을 손쉽게 배열로 변환할 수 있다.
// spread 문법
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 ]
4. Lazy Evaluation (지연 평가)
컴퓨터 프로그래밍에서 Lazy Evaluation은 계산의 결과 값이 필요할 때까지 계산을 늦추는 기법을 뜻한다.
- 이러한 지연평가는 크게 3가지 이점을 가진다.
- 불필요한 계산을 하지 않으므로 빠른 계산이 가능하다.
- 무한 자료 구조를 사용 할 수 있다.
- 복잡한 수식에서 오류 상태를 피할 수 있다.
- 이터러블을 통하여 이 지연 평가를 구현할 수 있다.
- 위에서 이터러블은 데이터 공급자의 역할을 한다고 했다. 배열이나 문자열 들은 모든 데이터 메모리에 미리 확보한 다음 데이터를 공급한다.
- 하지만 아래와 같은 코드로 이터러블의 지연평가를 이용하면, 데이터가 필요한 시점 이전까지는 미리 데이터를 생성하지 않다가, 데이터가 필요한 시점이 되면 그때 비로소 데이터를 생성한다.
- 즉 평가 결과가 필요할 때까지 평가를 늦추는 것이다.
const arr = [0, 1, 2, 3, 4, 5]
const result = _.take(2,
L.filter(num => num % 2,
L.map(num => num + 10, arr)))
console.log(result) // [11, 13]
- 지연 평가의 효과는 대상이 크면 클수록 그 효과를 발휘한다.
*이터러블을 만드는게 복잡해보였죠...? 그래서 자바스크립트는 이터러블을 보다 간편하게 생성할 수 있는 제너레이터 함수를 지원합니다.
*당연히 본 글 바로 다음 글은 제너레이터로 이어집니다 :)
https://haksae.tistory.com/135
참고자료
https://poiemaweb.com/es6-iteration-for-of
https://whyhard.tistory.com/m/49?category=1001393
https://armadillo-dev.github.io/javascript/whit-is-lazy-evaluation/
https://velog.io/@hangem422/js-iterable