1. 이터러블과 함수형 프로그래밍

최근 javascript 함수형 프로그래밍 을 공부하면서 알게 된 ES6 순회와 이터러블/이터레이터 프로토콜의 관계에 대해 공부하게 되었습니다. 제 생각으로는 함수형 프로그래밍에서 이터러블/이터레이터 프로토콜은 핵심적인 개념이라고 생각합니다. 생각보다 간단한 원리에서 시작한 개념이 함수형 프로그래밍에서 아주 요긴하게 쓰입니다. 본 게시글은 타 블로그 및 MDN에서 참조한 내용으로 작성되었습니다.

 

Iteration protocols - JavaScript | MDN

ECMAScript 2015 (ES6)에는 새로운 문법이나 built-in 뿐만이 아니라, protocols(표현법들)도 추가되었습니다. 이 protocol 은 일정 규칙만 충족한다면 어떠한 객체에 의해서도 구현될 수 있습니다.

developer.mozilla.org

2. 이터러블/이터레이터 프로토콜

이터러블/이터레이터 프로토콜은 MDN에서 iteration protocols 라고 칭하고 있습니다. 이터레이터가 무엇인지 알아보기 전에, 이터레이터가 어디에 쓰이고, 왜 쓰이는지 알아보겠습니다.

배열 순회

ES5 에서 for문을 통한 배열 순회는 아래와 같이 작성되었습니다.

const arr = [1, 2, 3, 4, 5];

for(let i = 0; i < arr.length; i++) {
  console.log(arr[i]); // 1, 2, 3, 4, 5
}

아래는 변경된 ES6 에서 배열 순회입니다.

const arr = [1, 2, 3, 4, 5];
const str = 'abc';
const mySet = new Set([1, 2, 3, 4, 5]);

for(const a of arr){ // for...of로 리스트 안에 있는 값(a)을 순회한다.
  console.log(a); // 1, 2, 3, 4, 5
}
for(const a of str){
  console.log(a); // a, b, c
}

for(const a of mySet) {
  console.log(a); // 1, 2, 3, 4, 5
}

하지만 기존 ES5 에서 사용되는 순회는 큰 문제점이 있습니다. 바로 for문으로 순회하기 위해 length 라는 속성에 의존 해야 한다는 것입니다. 즉, 순회하고자 한다면 꼭 __proto__ 또는 constructor 에서 length 를 프로토타입 기반으로 상속 을 받아야 한다는 점입니다. 아래 코드를 보시면 이해에 도움이 되실 것 같습니다.

const mySet = new Set([1, 2, 3, 4, 5]);

for(let i = 0; i < mySet.keys().length; i++) {
  console.log('hello');
}

그리고 mySet.keys() 의 값을 로그로 찍어보면 아래와 같습니다.

SetIterator {1, 2, 3, 4, 5}
0: 1
1: 2
2: 3
3: 4
4: 5

Set 형의 keys()length 프로퍼티를 가지고 있지 않기 때문에 for문에서 아무 로그도 나오지 않게 됩니다. 반환된 SetIterator 는 아래부터 설명하게 될 이터러블이터레이터 와 관련 있습니다.

이터러블

이터레이터를 리턴하는 Symbol.iterator() 라는 메서드를 가진 값

이터러블은 객체의 값을 반복 순회할 수 있는 자격을 가진 객체를 뜻합니다. js에서 객체가 이터러블이 되기 위해서는 해당 객체에 Symbol.iterator 메서드를 가지고 있어야 합니다. 예시 코드를 보여드리겠습니다.

const arr = [1, 2, 3];
arr[Symbol.iterator]; // ƒ values() { [native code] }, >> 메서드
arr[Symbol.iterator](); // Array Iterator {} >> 배열형 이터레이터 객체

Araay 형의 Symbol.iterator() 메서드로 이터레이터를 반환받았기 때문에 Array이터러블 하다 또는 이터러블 프로토콜을 준수했다. 라고 할 수 있습니다.

이터레이터

{ value, done } 객체를 리턴하는 next() 라는 메서드를 가진 값

이터러블 객체로부터 [Symbol.iterator]() 메서드를 이용하여 이터레이터를 반환받은 후 이터레이터 객체에 next() 메서드를 통해서 이터러블 객체들의 순회 정보를 확인할 수 있습니다.

const arr = [1, 2, 3];
let iterator = arr[Symbol.iterator](); // 반환받은 이터레이터 객체를 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}

for...of 문

이때 for of 문은 이터러블의 이터레이터를 받아와 next() 를 호출하면서 { value, done } 객체를 받아오고 value 의 값을 사용합니다. 그러다 donetrue 이면 순회를 종료합니다. 하지만 이미 다양한 오픈소스와 웹 API들, DOM 객체 관련 등 에도 이터러블 프로토콜을 이미 따르고 있기 때문에 해당 프로토콜을 이용해 순회를 하시면 되겠습니다.

const mySet = new Set([1, 2, 3, 4, 5]);
for(const a of mySet) {
  console.log(a); // 1, 2, 3, 4, 5
}

const nodeList = document.querySelectorAll('*');
for(const a of nodeList) {
  console.log(a); 
  // {value: html, done: false}
  // {value: head, done: false}
  // {value: meta, done: false}
  // ...
}

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

전개 연산자

이터러블 객체의 요소를 전개해줍니다 ...이터러블객체 키워드로 사용합니다.

const a = [1, 2, 3, 4];

console.log(...a);
// 1, 2, 3, 4

3. 사용자 정의 이터러블

새로운 이터러블 객체 만들어 보면서 이터러블에 대해 깊게 알아보겠습니다. 방법은 아래와 같습니다.
● Symbol.iterator() 메서드 구현하기
● 이터레이터의 반환 값을 자기 자신(이터레이터)로 설정

/* 사용자 정의 이터러블 */
const iterable = {
  [Symbol.iterator]() {
    let i = 3;
    return {
      next() {
        return i == 0 ? { done: true } : { value: i--, done: false };
      },
      [Symbol.iterator]() {
        return this; // 자기 자신을 return 한다.
      },
    };
  },
};

/* 이터러블 객체인지 for...of 문으로 확인하기 */
for (const a of iterable) console.log(a);
// 3
// 2
// 1

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

잘 만든 이터러블(well-formed Iterable)이란?

이터레이터를 만들어서 순회를 할 때 잘 동작한다.
이터레이터에게 Symbol.iterator() 메서드는 이터레이터 자기 자신을 반환한다.

'Javascript > ES6' 카테고리의 다른 글

[javascript] ES6 문법 활용 정리  (0) 2022.06.12
[javascript] ES6 구조분해 할당 총 정리  (2) 2021.09.27