[functional JS ES6+] 순회와 iterable

작성:    

업데이트:

카테고리:

태그: , ,

본 포스트는 인프런의 함수형 프로그래밍과 JavaScript ES6+ 강의(링크)를 듣고 정리한 내용입니다.


Arr, Set, Map

Arr

const arr = [1, 2, 3];
for (const a of arr) log(a); // 1 \n 2 \n 3
  • for of 문을 통해 각 원소들을 처리 가능
  • arr[i] 등을 통해 개별 원소에 접근 가능


Set

const set = new Set([1, 2, 3]);
for (const a of set) log(a); // 1 \n 2 \n 3
  • for of 문을 통해 각 원소들을 처리 가능
  • set[i] 등을 통해 개별 원소에 접근 불가능(undefined 발생)


Map

const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map) log(a); // ["a", 1] \n ["b", 2] \n ["c", 3]
  • 원소들이 [key, value]로 구성
  • for of 문을 통해 각 원소들을 처리 가능
  • map[i] 등을 통해 개별 원소에 접근 불가능(undefined 발생)


for (const a of map.keys()) log(a); // a \n b \n c
for (const a of map.values()) log(a); // 1 \n 2 \n 3
for (const a of map.entries()) log(a); // ["a", 1] \n ["b", 2] \n ["c", 3]
  • .keys() : key만 순회
  • .values() : value만 순회
  • .entries() : key, value 쌍으로 순회


Symbol.iterator

어떻게 array는 idx를 통해 개별 원소에 접근할 수 있을까?

바로 Symbol.iterator라는 함수 때문이다.


const arr = [1, 2, 3];
log(arr[Symbol.iterator]); // f values() { [native code] }
  • ES6에서 추가
  • Symbol은 어떤 객체의 key로 사용될 수 있다.


Symbol.iterator 함수를 비우고 for of 문으로 순회하면?

const arr = [1, 2, 3];
arr[Symbol.iterator] = null;
for (const a of arr) log(a); // TypeError: arr is not iterable
  • TypeError가 발생한다.
  • 즉, 순회가 불가능해진다는 말
  • 이는 Set과 Map 역시 동일하다.
  • Symbol.iterator와 for of문은 어떤 상관관계가 있지 않을까?


iterable/iterator protocol

  • array, set, map은 JS 내장객체
  • iterable/iterator protocol을 따름


iterable

iterator를 return하는 Symbol.iterator 메서드를 가진 값


iterator

  • { value, done } 객체를 return하는 next() 메서드를 가진 값
const arr = [1, 2, 3];
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}


iterator protocol

  • iterable을 for of문, 전개 연산자 등과 함께 동작하도록 한 규약


정리

  • array는 Symbol.iterator를 통해 iterator를 return하는 iterable이다.
  • iterable이므로 for of 문으로 순회할 수 있다.
  • 때문에 iterable/iterator protocol을 잘 따른다고 할 수 있다.


사용자 정의 iterable

기본 양식

const iterable = {
  // iterable은 [Symbol.iterator]() 메서드를 가지고 있어야 한다.
  [Symbol.iterator]() {
    return {
      // [Symbol.iterator]()는 next() 메서드를 반환
      next() {
        // next() 메서드는 value, done 객체를 반환
        return { value, done }
      }
    }
  }
}


로직 정의와 활용

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]();
log(iterator.next()); // {value: 3, done: false}
log(iterator.next()); // {value: 2, done: false}
log(iterator.next()); // {value: 1, done: false}
log(iterator.next()); // {done: true}

// done이 false인 동안 iterator 내의 값들을 출력
for (const a of iterator) log(a); // 3 \n 2 \n 1
  • next() 메서드를 추가해서 i를 변화시키며 return 값을 변화
  • i의 값에 따라 0에 다다르면 { done: true } 반환
  • 이 때문에 for of 문을 돌리면 done: true 일 때까지 value 값을 출력
  • well-formed iterator를 위해 자기 자신을 return하는 구문도 return문에 추가


well-formed iterator

const arr = [1, 2, 3];
let iter = arr[Symbol.iterator]();
log(iter[Symbol.iterator]() == iter); // true
  • iterator의 Symbol.iterator를 출력하면 자기 자신이 나온다.
  • 이런 경우 well-formed iterator라고 한다.


const arr = [1, 2, 3];
let iter = arr[Symbol.iterator]();
iter.next();
for (a of iter) log(a); // 2 \n 3
  • iter.next()를 한 번 진행하고 for of 문을 실행하면, Symbol.iterator에 의해 자기 자신이 반환되므로 next를 진행한 횟수만큼 순회를 건너뛰고 순회를 시작한다.


활용 범위

  • Symbol.iterable은 JS 내장 객체 뿐만이 아니라, Web API나 오픈소스 라이브러리에서도 구현이 되어가는 중
  • 때문에 iterator protocol을 따르므로 for of 문을 활용해 순회가 가능
  • 아래와 같은 예시도 사용 가능
// Web API의 경우
for (const a of document.querySelectorAll('*')) log(a);
// [결과]
// <html>...</html>
// <head>...</head>
// <script>...</script>
// <body>...</body>
// ...

const all = document.querySelectorAll('*');
log(all[Symbol.iterator]); // f values() { [native code] }
log(all[Symbol.iterator]()); // Array Iterator {}


전개 연산자(…)와 iterable

전개 연산자도 iterable/iterator protocol을 따른다

const a = [1, 2];
log([...a, ...[3, 4]]); // [1, 2, 3, 4]

a[Symbol.iterator] = null;
log([...a, ...[3, 4]]); // TypeError: not iterable
  • 전개연산자는 iterable protocol을 따르는 값들을 전개하는 것
  • 즉, Symbol.iterator을 null 처리하면 TypeError: not iterable 에러 발생
  • arr, set, map을 spread할 수 있다. 모두 iterable protocol을 따르므로
  • map.keys(), map.values() 도 전개할 수 있다. 이들도 iterable protocol을 따르므로

댓글남기기