JavaScript

[JavaScript] 함수형 프로그래밍 (이터러블, 이터레이터)

swimKind 2022. 3. 30. 00:39

[JavaScript] 함수형 프로그래밍 (이터러블, 이터레이터)

 

 

 

ES6부터 리스트를 순회하는 방법의 차이가 생겼다.

console.log('Array');
const arr = [1, 2, 3];
for(const a of arr) console.log(a); // 1 2 3

console.log('Set');
const set = new Set([1, 2, 3]);
for(const a of set) console.log(a); // 1 2 3

console.log('Map');
const map = ([['a', 1], ['b', 2], ['c', 3]]);
for(const a of map) console.log(a); // ["a", 1] ["b", 2] ["c", 3]
for(const a of map.keys()) console.log(a); // a b c
for(const a of map.values()) console.log(a); // 1 2 3
for(const a of map.entries()) console.log(a); // ["a", 1] ["b", 2] ["c", 3]

 

 

# 이터러블/이터레이터 프로토콜

 - 이터러블: 이터레이터를 리턴하는 [Symbol.iterator]() 를 가진 값

 - 이터레이터: { value, done } 객체를 리턴하는 next() 를 가진 값 (리절트 객체)

 - 이터러블 / 이터레이터 프로토콜: 이터러블을 for...of, 전개 연산자, 구조 분해 할당 등과 함께 동작하도록 만든 규약

arr[Symbol.iterator] // f values() { [native Code] }

let iterator = arr[Symbol.iterator]();
iterator.next() // { value: 1, done: false} 
iterator.next() // { value: 2, done: false} 
iterator.next() // { value: 3, done: false} 
iterator.next() // { value: undefined, done: true}

 

 

# 사용자 정의 이터러블을 통해 알아보기

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

let iterator = iterable[Symbol.iterator]();
iterator.next();
for(const a of iterable) console.log(a); // 2 1
for(const a of iterator) console.log(a); // TypeError: iterator is not iterable

이터러블 / 이터레이터 프로토콜을 사용하기위해 사용자가 정의할때는 위의 [Symbol.iterator]() 메서드를 정의하고 [Symbol.iterator]() 메서드는 next() 메서드를 반환해야하며 next() 메서드의 내용으로는 { value, done } 을 가지는 객체를 반환해야 한다.

 

하지만 위의 이터러블 / 이터레이터는 완벽하지 않다. Array나 Set, Map 등 다른 이터러블 프로토콜을 만족하는 것들의 내부를 보면 이터러블 프로토콜을 만족하면서 이터레이터 프로토콜을 만족한다. 그러한 이터러블 프로토콜을 well-formed 이터러블 프로토콜 이라고 한다.

 

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

let iterator = iterable[Symbol.iterator]();
iterator.next();
for(const a of iterable) console.log(a); // 2 1
for(const a of iterator) console.log(a); // 2 1

well-formed 이터러블 프로토콜은 이터러블을 for...of 문으로 순회를 해도 순회가되고 이터레이터로 만들어서 for...of 문으로 순회해도 순회가 된다. 또한 일정부분 next() 메서드를 통해 진행을 하고 순회를 해도 마찬가지로 순회가 된다.

 

console.log(document.querySelectorAll('*')) // NodeList(...) [...]
for(const a of document.querySelectorAll('*')) console.log(a); // <html>...</html> <head>...</head> ...
const all = document.querySelectorAll('*');
let iter = all[Symbol.iterator]();
iter.next(); // { value: html, done: false }
iter.next(); // { value: head, done: false }
iter.next(); // { value: script, done: false }

해당하는 이터러블 프로토콜은 오픈소스 라이브러리 뿐만 아니라 브라우저의 DOM 또한 이터러블 프로토콜을 만족한다.

 

 

# 전개연산자

const a = [1, 2];
// a[Symbol.iterator] = null; // null이라면 아래 코드 TypeError
console.log([...a, ...arr, ...set, ...map.keys()]); // [1, 2, 1, 2, 3, 1, 2, 3, "a", "b", "c"]

자바스크립트에서 ES6의 이터러블 / 이터레이터 프로토콜은 중요하다. 해당하는 프로토콜을 만족하면 기존에 ES5에서 사용하던 자료구조보다 더 다양하고 쉬운 방식으로 데이터들을 핸들링 할 수 있다.