한 걸음씩 기록하며

[Iteration, Iterable, Iterator]이란 무엇인가 본문

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가지 이점을 가진다.
    1. 불필요한 계산을 하지 않으므로 빠른 계산이 가능하다.
    2. 무한 자료 구조를 사용 할 수 있다.
    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

 

[Generator]란 무엇인가

 

haksae.tistory.com

 

참고자료

https://poiemaweb.com/es6-iteration-for-of

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Iteration_protocols#iterable_%EC%98%88%EC%8B%9C

https://whyhard.tistory.com/m/49?category=1001393

https://armadillo-dev.github.io/javascript/whit-is-lazy-evaluation/

https://velog.io/@hangem422/js-iterable

 

 

 

 

 

 

Comments