본 포스트는 인프런의 함수형 프로그래밍과 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을 따르므로
