[functional JS ES6+] 순회와 iterable
작성:    
업데이트:
카테고리: Functional JS
태그: FE Language, Functional JS, JS
본 포스트는 인프런의 함수형 프로그래밍과 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을 따르므로
댓글남기기