logo
Search검색어를 포함하는 게시물들이 최신순으로 표시됩니다.
    Table of Contents
    [functional JS ES6+] 순회와 iterable

    이미지 보기

    [functional JS ES6+] 순회와 iterable

    • 22.06.03 작성

    • 읽는 데 7

    TOC

    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.iterator]()은 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을 따르므로
    profile

    FE Developer 박승훈

    노력하는 자는 즐기는 자를 이길 수 없다