본문 바로가기

Javascript

사용자 정의 이터러블, 이터러블 / 이터레이터 프로토콜 정의 - JS ES6+ 함수형 프로그래밍 - 6

본 시리즈는 인프런 강의 함수형 프로그래밍과 JavaScript ES6+ ( 지식 공유자 : 유인동 님 ) 의 강의를 수강하면서 내용을 제 방식대로 포스팅하는 글입니다.

 

함수형 프로그래밍과 JavaScript ES6+ - 인프런 | 강의

ES6+와 함수형 프로그래밍을 배울 수 있는 강의입니다. 이 강좌에서는 ES6+의 이터러블/이터레이터/제너레이터 프로토콜을 상세히 다루고 응용합니다. 이터러블을 기반으로한 함수형 프로그래밍,

www.inflearn.com

사용자 정의 이터러블을 구현하면서 이터러블에 대해서 더 정확하게 알아보자

// iterable 이라고 하는 값을 직접 정의해보자
const iterable = {
  // iterable 값은 Symbol.iterator 를 구현하고 있어야 됀다.
  // Symbole.iterator 는 이터레이터를 반환 해야 한다.
  [Symbol.iterator]() {
    let i = 3;

    // 이터레이터는 next 메소드를 가지고 있으며
    return {
      // next 는 value 와 done 을 가진 객체를 리턴해야한다.
      next() {
        return i == 0 ? { done: true } : { value: i--, done: false };
      },
    };
  },
};

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

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

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

이터러블의 Symbol.iterator 가 구현되어 있기 때문에 for ... of 문에 들어갈수 있고,

안에서 Symbol.iterator 를 실행했을때 객체가 리턴되는것이고, 

for ... of 문에서 내부적으로 next() 가 실행되면서 그 리턴문이 실행된다.

하지만 JS 이터러블의 모든 속성을 구현하지는 않았다.

const arr1 = [1, 2, 3];

// 잘 구현된 이터러블은, 이터레이터를 만들었을때
// 이터러블을 진행하다가 순회할수도 있고
// 그대로 값을 넣어도 처음부터 잘 실행된다

let iter1 = arr1[Symbol.iterator]();

// 이터러블의 이터레이터를 실행했을때, 자기 자신을 가지고 있을때
// well formed iterator, well formed iterable 이라고 할수있다.
console.log(iter1[Symbol.iterator]() === iter1); // true

iter1.next();
for (const b of iter1) console.log(b); // 2 3

다시 커스텀 이터러블을 구현하면, 

const iterable = {
  // iterable 값은 Symbol.iterator 를 구현하고 있어야 됀다.
  // Symbole.iterator 는 이터레이터를 반환 해야 한다.
  [Symbol.iterator]() {
    let i = 3;

    // 이터레이터는 next 메소드를 가지고 있으며
    return {
      // next 는 value 와 done 을 가진 객체를 리턴해야한다.
      next() {
        return i == 0 ? { done: true } : { value: i--, done: false };
      },
      // Symbol.iterator 를 실행했을때 리턴된 iterator 가
      // 자기 자신또한 이터러블 이면서, 어디에서든 이터레이터로 만들었을때
      // 이전까지 진행되어있던 자기 상태에서 계속해서 next() 를 할수 있도록 만들어 둔것이
      // 잘 만든 이터레이터 인것이다.
      [Symbol.iterator]() {
        return this;
      },
    };
  },
};

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

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

// 이터러블도 순회가 되고, 이터레이터도 순회가 되고
// 일정부분 진행한 후에 실행해도 순회가 되도록 하는것이 잘 만든 이터레이터이다.
for (const a of iterable) console.log(a); // 3 2 1
for (const a of iterator) console.log(a); // ( 자기 자신이 반환되지 않았을 경우 ) TypeError iterator is not iterable

이 이터러블 / 이터레이터 프로토콜은 es6 에서 지원하는 내장 값만 이 프로토콜을 따르는 것이 아니다.

이미 많은 오픈소스 라이브러리, 어떤 JS 에서 순회가 가능한 형태의 값을 가진 값들은 대부분 이터러블 / 이터레이터 프로토콜을 따른다.

예로, 페이스북에서 만든 Eutable.js 의 경우에도 for of 문을 통해 순회할수 있도록 심볼 이터레이터가 구현되어 있다.

이런 오픈소스만 이터러블 / 이터레이터 프로토콜을 따르는 것이 아니라 JS 를 사용할수 있는 환경인 브라우저에서 사용할수 있는 Web APIs 들에 있는 구현되어 있는 많은 값들, 브라우저에서 사용할수 있는 DOM 과 관련된 값들 이라든지 여러 가지 값들도 이 이터러블 / 이터레이터 프로토콜을 따르고 있다.

예를 들면, NodeList 객체가 있다.

console.log(document.querySelectorAll("*")); // NodeList(n)[html,head,body...]

const all = document.querySelectorAll("*");

for (const c of all) console.log(c); // <html> <head> <body> ...
console.log(all[Symbol.iterator]()); // Array iterator{}

let iter2 = all[Symbol.iterator]();
console.log(iter2.next()); // {value : html, done: false}
console.log(iter2.next()); // {value : head, done: false}
console.log(iter2.next()); // {value : body, done: false}

NodeList 또한 이터러블 / 이터레이터 프로토콜을 따르고 있다 !

JS 에서 새롭게 바뀐 순회 그리고 이터러블 / 이터레이터 프로토콜은 굉장히 중요하다