Iterable 반복 가능한 객체

Posted by : at

Category : JavaScript


Iterable 과 Symbol.iterator는 무엇일까?


1. for…of를 이용한 리스트 순회

반복 가능한 객체(iterable object)는 for…of 구문과 함께 ES2015에서 도입되었습니다.
반복 가능한 객체를 다른 객체와 구분짓는 특징은, 객체의 Symbol.iterator 속성에 특별한 형태의 함수가 들어있다는 것입니다.

우선 es6에서 도입된 for...of 문의 순회 방법을 살펴봅시다.

const numbers = [1, 2, 3, 4];
// 기존 방식
for (let i = 0; i < numbers.length; i++) {
  console.log(numbers[i]);
}
// 결과: 1, 2, 3, 4

// for ... of
for (const n of numbers) {
  console.log(n);
}
// 결과: 1, 2, 3, 4

확인을 해보면 순회를 하는방법이 보다 선언적이고 간결해진 느낌을 받을수 있습니다. 하지만 for...of문은 단순하게 복잡한 for loop를 간결하게 만든것 이상의 의미들을 가지고 있습니다. es6에서 어떤 규약이 생겼고 순회에 대해서 어떤식으로 작동하는지 알아봅시다.


2. Array와 Set을 통해 알아보는 Iterable Protocol

객체의 Symbol.iterator 속성에 특정 형태의 함수가 들어있다면, 이를 반복 가능한 객체(iterable object) 혹은 줄여서 iterable이라 부르고,
해당 객체는 iterable protocol을 만족한다고 말한다. 이런 객체들에 대해서는 ES2015에서 추가된 다양한 기능들을 사용할 수 있습니다.

내장된 생성자 중 iterable 객체를 만들어내는 생성자에는 아래와 같은 것들이 있습니다.

- String
- Array
- TypedArray
- Map
- Set
- arguments (내장 객체는 아님)
- DOM NodeList (내장 객체는 아님)

어떤 객체가 Iterable이라면, 그 객체에 대해서 아래의 기능들을 사용할 수 있습니다.

- for...of 루프
- spread 연산자 (...)
- 비구조화 할당(destructuring assignment)
- 기타 iterable을 인수로 받는 함수

iterable protocol을 만족하는 객체인 ArraySet을 이용해 for…of문의 순회 방식에 대해 알아봅시다.

const arr = [1, 2, 3];
for (const n of arr) {
  console.log(n);
}
// 결과: 1, 2, 3

const set = new Set([1, 2, 3]);
for (const n of set) {
  console.log(n);
}
// 결과: 1, 2, 3

위의 결과에서 처럼 for…of 문을 사용해서 순회를 하면 두 객체 모두 1, 2, 3 을 출력할수 있습니다. 하지만 우리는 여기서 한가지 더 확인해봐야 할 사실이 있습니다. Set은 Array처럼 인덱스를 통해서 요소에 접근할수 없기 때문입니다.

const set = new Set([1, 2, 3]);
console.log(set[0]);
// 결과: undefined

이러한 결과를 볼때 for...of 의 순회방식은 기존에 사용해오던 for문과는 다른 방식으로 순회를 하는것을 알수 있습니다.
for…of 명령문이 set을 순회할수 있는 이유는 Set은 iterable이고 for..of문은 Iterable Protocol을 따르기 때문이다.
iterable protocol을 이해하기 위해 Symbol.iterator 에 대해 살펴봅시다.

const arr = [1, 2, 3];
console.log(arr);
Script
arr Array(3)
0 1
1 2
2 3
length 3
[[prototype]] Array(0)
Symbol(Symbol.iterator) f values()

위와같이 Array의 구조를 살펴보면 Prototype객체의 속성중에 Symbol(Symbol.iterator)를 발견할수 있습니다.
Symbol(Symbol.iterator) 는 f values() 라는 어떠한 함수를 뜻하고있습니다. 아래의 예제코드를 통해 이것의 용도를 알아봅시다.

const arr = [1, 2, 3];
arr[Symbol.iterator] = null;
for (const n of arr) {
  console.log(n);
}
// 결과: Uncaught TypeError: arr is not iterable

Symbol.iterator를 null로 할당했더니 for…of문이 수행하지못하고 arr 이 이터러블이 아니다 라는 에러를 출력합니다.
이를 통해 iterable 프로토콜과 Symbol.iterator 가 어떠한 연관이 있다고 추론해볼수 있습니다.

우선 아래의 글을 통해 iterable이 무엇인지 살펴봅시다.

- iterable: 배열을 일반화한 객체, iterator 를 리턴하는 Symbol.iterator(특수 내장 심볼)를 가진 값
- iterator: iterator(iterator, 메서드 next가 있는 객체), next() 는 { value, done }을 리턴한다.
- 이터러블/이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자 등과 함께 동작하도록 한 규약.
- for..of는 반환된 객체(이터레이터)만을 대상으로 동작한다.
const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

// 결과: {value: 1, done: false}
// 결과: {value: 2, done: false}
// 결과: {value: 3, done: false}
// 결과: {value: undefined, done: true}

위의 코드에서 처럼 arr 의 Symbol.iterator 를 실행시켰을경우 이 함수는 Iterator를 반환하게 되고, iterator는 next함수를 가지고 있습니다. next함수를 실행히켜보면 순차적으로 value 와 순회의 상태를 알려주는 done을 포함한 객체가 리턴이 되고 for…of는 이러한 방식을 이용해서 iterable을 순회하고 value를 순회의 값으로 사용할수 있습니다.

그러므로 인덱스를 통해 값을 참조할수없는 Setiterable 프로토콜을 통해 순차적으로 값을 출력하게 됩니다.

const set = new Set([1, 2, 3]);
const iter = set[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

// 결과: {value: 1, done: false}
// 결과: {value: 2, done: false}
// 결과: {value: 3, done: false}
// 결과: {value: undefined, done: true}

3. 사용자 정의 이터러블

이번에는 사용자 정의 이터러블을 구현해보면서 iterable에 대해 더 정확히 알아봅시다.

// iterable 객체 생성
const iterable = {
  // iterator를  리터하는 Symbol.iterator 함수 정의
  [Symbol.iterator]() {
    let i = 0;
    return {
      next() {
        return i === 3
          ? { value: undefined, done: true }
          : { value: i++, done: false };
      },
    };
  },
};

const iter = iterable[Symbol.iterator]();
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());
console.log(iter.next());

// 결과: {value: 1, done: false}
// 결과: {value: 2, done: false}
// 결과: {value: 3, done: false}
// 결과: {value: undefined, done: true}

for (const a of iterable) {
  console.log(a);
}
// 결과: 0, 1, 2

위에서 정의한 사용자 정의 이터러블은 Symbol.iterator 를통해 iterator를 반환해서 next를 실행시킬수 있고, for...of문을 통해 순회를 할수 있습니다.

4. Well-formed-iterable

위의 사용자 정의 이터러블은 겉으로보기엔 잘 동작하는것같지만 중요한 포인트가 빠졌습니다. 이는 Symbol.iterator를 통해 리턴받은 iterator가 자기 자신이며 iterable일 경우에 Well-formed-iterable라 하는데 내장된 iterable 객체들이 이러한 방식으로 구성되어 있습니다. 아래 코드를 살펴봅시다.

// Well-formed-iterable (배열)
const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();
// Symbol.iterator를 통해 리턴받은 iterator를 iterable로 사용
for (const n of iter) {
  console.log(n);
}
// 결과: 0, 1, 2
// 또한 Well-formed-iterable 은 iterable[Symbol.iterable]() === iterator === 자기 자신
// 이라는 특성때문에 next를 수행했다면 수행한 이후부터 순회를 시작한다.
const arr = [1, 2, 3];
const iter = arr[Symbol.iterator]();
iter.next();
for (const n of iter) {
  console.log(n);
}
// 결과: 1, 2

// Symbol.iterable를 실행 후 리턴받은 iterator는 자기자신
const newIter = iter[Symbol.iterator]();
console.log(iter === newIter);
// 결과: true

이번에는 이전에 만들었던 사용자 정의 이터러블을 Well-formed-iterable로 만들어 봅시다.

const iterable = {
  [Symbol.iterator]() {
    let i = 0;
    return {
      // iterator가 Symbol.iterator를 실행시켰을 경우 자기 자신을 리턴
      [Symbol.iterator]() {
        return this;
      },
      next() {
        return i === 3
          ? { value: undefined, done: true }
          : { value: i++, done: false };
      },
    };
  },
};

const iter = iterable[Symbol.iterator]();
iter.next();
for (const n of iter) {
  console.log(n);
}
// 결과: 1, 2

// 만약 iterator에 Symbol.iterator함수를 정의 하지 않았다면
// Uncaught TypeError: iter is not iterable 에러가 발생한다

간단요약
  • for..of을 사용할 수 있는 객체를 이터러블이라고 부른다.
  • 이터러블엔 메서드 Symbol.iterator가 반드시 구현되어 있어야 한다.
  • obj[Symbol.iterator]의 결과는 이터레이터라고 부른다. 이터레이터는 이어지는 반복 과정을 처리한다.
  • 이터레이터엔 객체 {done: Boolean, value: any}을 반환하는 메서드 next()가 반드시 구현되어 있어야 한다.
  • iterator는 for..of에 의해 자동으로 호출되는데, 개발자가 명시적으로 호출하는 것도 가능하다.

About Jack koh
Jack koh

안녕하세요 프론트엔드 개발자 고현영입니다.

Email : rhgusdud09@naver.com

Website : https://github.com/Jack-koh

About Jack koh

안녕하세요 프론트엔드 개발자 고현영입니다.

Star
Categories
Useful Links