34장 이터러블

niyu·2021년 7월 16일
0
post-thumbnail

이터러블과 이터레이터

ES6 이전의 배열, 문자열, DOM 컬렉션 등의 순회할 수 있는 자료구조는 통일된 규약 없이 각자 나름의 구조를 가지고 for 문, for...in 문 forEach 메서드 등의 다양한 방법으로 순회할 수 있었다.

ES6에서는 이들을 이터러블이라는 것으로 통일해 for...of 문, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용할 수 있도록 일원화했다.

이터러블 (iterable)

이터러블은 Symbol.iterator를 프로퍼티 키로 사용한 메서드를 직접 구현하거나 프로토타입 체인을 통해 상속받은 객체다. 즉, 요소들을 탐색(반복)할 수 있는 데이터 구조를 말한다. 배열, 문자열, Map, Set은 모두 이터러블이다.

const array = [1, 2, 3];

// Symbol.iterator 메서드를 상속받는다.
console.log(Symbol.iterator in array); // true

📚 for...of 문 사용 가능

const array = [1, 2, 3];
for(const item of array) {
  console.log(item);
}

📚 스프레드 문법으로 배열 전환 가능

const array = [1, 2, 3];
console.log([...array]); // [1, 2, 3]

📚 디스트럭처링 할당 가능

const array = [1, 2, 3];
const [a, ...rest] = array;
console.log(a, rest); // 1, [2, 3]

📚 Array.from() 으로 배열 전환 가능 : Array.from 메서드는 이터러블한 객체가 들어오면 배열로 변환한다.

console.log(Array.from([1, 2, 3])); // [1, 2, 3]
console.log(Array.from(new Map([['a', 1], ['b', 2], ['c', 3]]))); //  [Array(2), Array(2), Array(2)]
console.log(Array.from(new Set([1, 2, 3]))); // [1, 2, 3]
console.log(Array.from('123')); // ['1', '2', '3']

객체가 이터러블한지 확인하는 방법은 해당 객체에 Symbol.iterator가 있는지, 그리고 Symbole.iterator가 함수인지를 검사해 확인할 수 있다.

const isIterable = (target) => typeof target[Symbol.iterator] === 'function';
console.log(isIterable({})); // false
console.log(isIterable([]); // true

이터레이터(iterator)

반복을 위해 설계된 특별한 인터페이스를 가진 객체다. 이터러블의 Symbol.iterator 메서드를 호출하면 이터레이터를 반환한다. 이터레이터는 next 메서드를 갖는다.

이터레이터의 next 메서드는 이터러블의 각 요소를 순회하기 위한 포인터의 역할을 한다. 이터레이터의 next 메서드가 반환하는 객체의 value 프로퍼티는 현재 순회 중인 이터러블의 값을 나타내고, done 프로퍼티는 이터러블의 순회 완료 여부를 나타낸다.

const array = [1, 2, 3];

// Symbol.iterator 메서드는 이터레이터를 반환
const iterator = array[Symbol.iterator]();
console.log(iterator); // Array Iterator {}

// next 메서드를 갖는다.
console.log('next' in iterator); // true

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 문, 스프레드 문법, 배열 디스트럭처링 할당, Array.from 메서드는 내부적으로 이터러블의 Symbol.iterator 메서드를 호출해 반환된 이터레이터를 가지고 done이 true가 되기 전까지 next 메서드를 반복 호출하는 로직으로 각각의 값을 도출한다.

이터러블과 이터레이터

const iterator = {
  items: [10, 20, 30],
  count: 0,
  next () {
    const done = this.count >= this.items.length;
    return {
      value: !done ? this.items[this.count++] : undefined,
      done
    }
  }
}
iterator.next(); // {value: 10, done: false}
iterator.next(); // {value: 20, done: false}
iterator.next(); // {value: 30, done: false}
iterator.next(); // {value: undefined, done: true}

위의 코드처럼 직접 이터레이터를 구현해서 사용할 수 있다.

유사 배열 객체

유사 배열 객체는 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체를 말한다.

유사 배열 객체는 length 프로퍼티를 갖기 때문에 for 문으로 순회할 수 있고, 인덱스를 나타내는 숫자 형식의 문자열을 프로퍼티 키로 가지기 때문에 인덱스로 프로퍼티 값에 접근할 수 있다.

const arrayLike = {
  0: 1,
  1: 2,
  2: 3,
  length: 3
};

for (let i = 0; i < arrayLike.length; i++) {
  console.log(arrayLike[i]); // 1 2 3
}

하지만 유사 배열 객체는 이터러블이 아닌 일반 객체다. 따라서 Symbol.iterator 메서드가 없기 때문에 for...of 문으로 순회할 수 없다.

for (const item of arrayLike) {
  console.log(item); // 1 2 3
}

// TypeError: arrayLike is not iterable

사용자 정의 이터러블

일반 객체도 이터러블로 만들 수 있다.

간단한 사용자 정의 이터러블을 구현해 보자.

🧩 사용자 정의 이터러블 구현

const fibonacci = {
  [Symbol.iterator]() {
    let [pre, cur] = [0, 1];
    const max = 10; // 수열의 최대값
    
    return {
      next() {
        [pre, cur] = [cur, pre + cur];
        return { value: cur, done: cur >= max };
      }
    };
  }
};

for (const num of fibonacci) {
  console.log(num); // 1 2 3 5 8
}

피보나치 수열(1, 2, 3, 5, 8, ...)을 구현하였다.

사용자 정의 이터러블은 Symbol.iterator 메서드를 구현하고 Symbol.iterator 메서드가 next 메서드를 갖는 이터레이터를 반환하도록 한다. 또한 이터레이터의 next 메서드는 value와 done 프로퍼티를 가지는 객체를 반환한다.

🧩 이터러블을 생성하는 함수 구현

수열의 최대 값을 외부에서 전달할 수 있도록 수정해 보자. 수열의 최대값을 인수로 전달받아 이터러블을 반환하는 함수를 만들면 된다.

const fibonacciFunc = function (max) {
  let [pre, cur] = [0, 1];
  
  // 이터러블 반환
  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
}

🧩 이터러블이면서 이터레이터인 객체를 생성하는 함수 구현

fibonacciFunc 함수는 이터러블을 반환하기 때문에, 이터레이터를 생성하려면 이터러블의 Symbol.iterator 메서드를 호출해야 한다.

const iterable = fibonacciFunc(5);

const iterator = iterable[Symbol.iterator]();

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

Symbol.iterator 메서드를 호출하지 않아도 되도록 코드를 수정해보자. fibonacciFunc 함수를 이터러블이면서 이터레이터인 객체를 생성하여 반환하는 함수로 변경하면 된다.

const fibonacciFunc = function (max) {
  let [pre, cur] = [0, 1];

  return {
    [Symbol.iterator]() { return this; },
    
    next() {
      [pre, cur] = [cur, pre + cur];
      return { value: cur, done: cur >= max };
    }
  };
};

let iter = fibonacciFunc(10);

// iter는 이터러블
for (const num of iter) {
  console.log(num); // 1 2 3 5 8
}

iter = fibonacciFunc(10);

// iter는 이터레이터
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 }



참고: Javascript ES6+ Iterable, Iterator - 정재남님 강의

0개의 댓글