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


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

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

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 쌍으로 순회

어떻게 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문은 어떤 상관관계가 있지 않을까?

  • 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을 잘 따른다고 할 수 있다.

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문에 추가

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/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을 따르므로