javascript/함수형프로그래밍과 ES6

Iterator 과 iterable, iterable 프로토콜에 대하여

윤-찬미 2021. 7. 25. 17:52

📝 서론

이터레이터 이터러블 이터러블 프로토콜에 대해 js 개발자라면 굉장히 많이 들어보셨을 것 같습니다.

다양한 반복스러운 일(?) 들에 대해 조금 더 스마트하게 작성할 수 있는 방법들을 고민하다가 이터러블 프로토콜을 적용시켜 코드를 작성해야겠다! 라고 생각하며, 조금 살펴본 간단한 개념들에 대해 정리하겠습니다.

TIL 정도의 분량입니다.

💁🏼‍♀️ 이터러블? 이터레이터? 이터러블 프로토콜? 그게 뭔데?

알고보면 간단한(사실 안간단) 개념인데 처음 접했을때는 왜이리 어렵고 복잡하게 느껴졌는지 모르겠습니다.

이터러블?

이터러블은?

이터레이터를 리턴하는 Symbol.iterator를 가진 값 입니다.

const iterable = {
    [Symbol.iterator]() {
    }
}

이터레이터?

{value, done} 객체를 리턴하는 next() 를 가진 값 입니다.

const iterable = {
    [Symbol.iterator]() {
        let i = 3;
    return {
      next() {
        return i === 0 ? { done: true } : { value: i--, done: false }
      }
      }
    }
}

위 코드를 보면 이터러블은 이터레이터를 리턴하는 [Symbol.iterator]를 가지고 있으며,

이터레이터는 {value, done} 객체를 리턴하는 next() 를 가지고 있습니다.

이터러블 프로토콜?

이터러블/이터레이터 프로토콜은 이터러블을 for...of, 전개연산자 등과 함께 동작하도록한 규약 입니다.

따라서 아래 코드는 이터러블 프로토콜을 따르고 있기 때문에 for..of로 순회가 가능합니다.

const iterable = {
    [Symbol.iterator]() {
        let i = 3;
    return {
      next() {
        return i === 0 ? { done: true } : { value: i--, done: false }
      }
      }
    }
}

for(a of iterable) {
    console.log(a);
}
// 3
// 2
// 1

for of 는 시작하자마다 [Symbol.iterator]를 실행하여 이터레이터를 리턴합니다.

그리고 돌면서 done이 false 가 될때까지 순회 합니다.

따라서 위와 같이 순회가 가능한 것 입니다.

실제로 여러분이 알고계신 array, string, nodeList 등이 이터러블 프로토콜을 따르고 있습니다.

const arr = [3, 2, 1];

for(a of arr) {
    console.log(a);
}
// 3
// 2
// 1

이터러블 프로토콜을 따르고 있는 객체라면 어떤 객체든 for of 적용이 가능합니다.

well-formed iterable

우리가 구현한 이터러블은 array, string, nodeList 등에서 구현된 이터러블이랑은 조금 다릅니다.

array, string, nodeList등은 well-formed iterable 입니다.

어떤 말이냐면 이터러블을 통해 이터레이터가 리턴된, 그 이터레이터는 이터레이터 이자,
또 자신을 리턴하는 [Symbol.iterator]를 가진 이터러블이라는 것입니다.

이를 well-formed iterable 이라 부릅니다.

코드로 표현하면 아래와 같습니다.

const iterable = {
  [Symbol.iterator]() {
    let i = 3;
    return {
      next() {
        return i === 0 ? { done: true } : { value: i--, done: false }
      },
      [Symbol.iterator]() {
        return this;
      }
    }
  }
}

이렇게 만든 이터러블은 아래와 같이 사용할 수 있습니다.

const iter2 = iterable[Symbol.iterator]();
for (it of iter2) {
  console.log(it);
}
// 3
// 2
// 1

제가 이터레이터를 만들고 for of 를 넣는데 사실 위와 같은 경우는 아래와 같이 써도 되긴 합니다.

for (it of iterable) {
  console.log(it);
}
// 3
// 2
// 1

근데 well-formed 한 이터러블을 만들면 이터러블이 진행된 시점을 기억하고,

그 시점부터 다시 시작 할 수 있습니다.

예를 들어, 아래와 같습니다.

const iter2 = iterable[Symbol.iterator]();
iter.next(); // ---> 여기서 3 실행됨
for (it of iter2) {
  console.log(it);
}
// 2
// 1

iter2 는 자신을 리턴하는 Symbol.iterator를 가진 이터러블 이기때문에 for of 로 순회할 수 있습니다.

array가 well-formed iterable 이라는 증거는 아래와 같이 증명할 수 있습니다.

const arrayIterable = [3, 2, 1][Symbol.iterator]();
arrayIterable[Symbol.iterator]() == arrayIterable; // true

전개연산자

전개 연산자 또한 이터러블/ 이터레이블 프로토콜을 따르고 있습니다.

따라서 아래와 같이 전개연산자를 사용할 수 있습니다.

const arr = [1, 2, 3]
const arr2 = [4, 5, 6]

console.log([...arr, ...arr2]);
// [1, 2, 3, 4, 5, 6]