JavaScript ES6+ 중급(3) iterable, iterator

Updated:

이 글은 정재남 개발자님의 인프런 강의인 JavaScript ES6+ 제대로 알아보기 중급편을 정리한 내용입니다.

1. iterable 개념

iterable은 내부 요소들을 공개적으로 반복할 수 있는 데이터 구조로서, __proto__Symbol.iterator 메소드를 갖고 있다. Symbol.iterator를 호출하면 iterator를 반환하는데, next 메소드를 호출하면 value와 done의 객체를 뱉는다!!! iterable한 것들에는 array, map, set, string, generator 등이 해당하는데, object는 해당하지 않는다.

iterable의 특징 (1) Array.from 메소드를 통해 배열로 전환이 가능하다.

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);

console.log(Array.from(map));
// 0: ["a", 1]
// 1: ["b", 2]
// 2: ["c", 3]

(2) 펼치기 연산자(spread operator)를 통해 배열로 전환이 가능하다.

const arrFromMap = [...map];

(3) 해체 할당이 가능하다

const [mapA, ,mapC] = map; 

(4) for … of를 사용 가능하다.

for(let x of [1, 2, 3]) {
  console.log(x);
}

// 1
// 2
// 3

(5) Promise.all, Promise.race 명령을 수행할 수 있다

const iterableVar = [
  new Promise((resolve, reject) => { setTimeout(resolve, 5000, 10)}),
  new Promise((resolve, reject) => { setTimeout(resolve, 3000, 20)}),
  1004,
  '가나다라',
  new Promise((resolve, reject) => {setTimeout(resolve, 4000, 30)}),
];

Promise.all(iterableVar)
	.then(value => console.log(v));
	.catch(error => console.log(error));

// [10, 20, 1004, '가나다라', 30]
// Promise.all은 인자로 iterable을 받으며, 모든 결과가 다 나올 때 then 구문을 실행한다.

(6) generator - yield* 문법으로 이용이 가능하다(yield*은 각각을 yield로 만들라는 것과 같다)

const arr = [1, 2, 3];

const makeGenerator = iterable => function* (){
  yield* iterable;
  // yield 1
  // yield 2
  // yield 3
}

const arrGen = makeGenerator(arr)();

위의 로직들은 [Symbol.iterator]로 반환되는 iterator의 next() 메소드를 기반으로 작동된다! 😳

2. iterator

iterator는 반복을 위해 설계된 특별한 인터페이스를 가진 객체이다.

  • iterator의 객체 내부에는 next() 메소드가 존재.
  • next() 메소드는 valuedone 프로퍼티를 지닌 객체를 반환
  • done 프로퍼티는 boolean이다.

아래는 iterator 형태의 예시이다.

const iterator = {
  items: [1, 2, 3],
  count: 0,
  next() {
    const done = this.count >= this.items.length;
    return {
      done,
      value: !done? this.items[this.count++]: undefined
    }
  }
};

console.log(iter.next());
// {done: false, value: 1}

위와 같이 정의한 iterator를 object에 [Symbol.iterator] 정의해 반환토록 하면 iterable한 데이터 구조가 사용할 수 있는 해체 할당 등을 사용할 수도 있다. 아래에서 규칙에 맞게 iterator를 구현해 보자. 규칙을 정리하면 다음과 같다.

  • object는 Symbol.iterator 메소드가 없다.
  • 그래서 Symbol.iterator 메소드를 추가해주면 object는 iterable하게 될 수 있다.
  • 대신 Symbol.iterator 메소드는 iterator를 반환해야 하는데,
  • 이 iterator는 next 메소드를 갖는 객체여야 한다.
  • next 메소드를 호출하면 value와 done 프로퍼티를 가지는 object를 반환한다!
  • object는 이제 iterable이다!!!😭
const createIterator = function() {
  let cnt = 0;
  const items = Object.entries(this);
  return {
    next() {
      return {
        done: cnt >= items.length,
        value: items[cnt++]
      }
    }
  }
};

const obj = {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
  [Symbol.iterator]: createIterator
}
console.log(...obj);
// (2) ["a", 1] (2) ["b", 2] (2) ["c", 3] (2) ["d", 4]

3. 정리

  • for…of, spread operator, forEach 등은 내부적으로
  • Symbol.iterator를 실행한 결과로서 객체를 들고 있으면서
  • 그 객체 내부의 next() 메소드를 done이 true가 될 때까지 반복적으로 호출한다.
  • iterator를 정해진 규칙에 맞게 구현할 수 있다는 점에서 덕 타이핑(duck typing)이라 할 수 있다.

Updated:

Leave a comment