JavaScript-함수형 언어 (2)
Array, Set, Map을 통한 iterable/iterator 프로토콜
- 자바스크립트엔 Array, Set, Map이라는 내장객체들이 존재한다.
- 이 객체들을 전부 Symbol.iterator를 통해 for…of 구문으로 순회할 수 있다.
- 만약 arr[Symbol.iterator]를 null로 한 다음 순회를 하려하면 순회를 할 수 없다.
- Symbol.iterator는 순회를 도와주는 함수이다.
// Array
const arr = [1, 2, 3];
for (const a of arr) console.log(a); // 1
// 2
// 3
// Set
const set = new Set([1, 2, 3]);
for (const a of set) console.log(a); // 1
// 2
// 3
// Map
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
for (const a of map) console.log(a); // ["a", 1]
// ["b", 2]
// ["c", 3]
- 하지만 여기서 set이나 map은 set[0] 또는 map[0]으로 조회가 불가능(undefined)하다.
- 단순 배열과 같은 순회방식이 아니기 때문이다.
- iterable : 이터레이터를 리턴하는
[Symbol.iterator]()
를 가진 값 (여기선 arr, set, map) - iterator : { value, done } 객체를 리턴하는 next() 메소드를 가진 값
- iterable / iterator 프로토콜 : iterable을 for…of, 전개연산자 등으로 동작하도록한 규약
- 그래서 set이나 map은 iterable / iterator 프로토콜 규약을 따르기 때문에 for…of문으로 순회가 가능한 것이다.
- 아래 코드는 위의 코드의 결과와 똑같다.
const arr = [1, 2, 3];
let iterator1 = arr[Symbol.iterator]();
for (const a of iterator1) console.log(a);
const set = new Set([1, 2, 3]);
let iterator2 = set[Symbol.iterator]();
for (const a of iterator2) console.log(a);
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
let iterator3 = map[Symbol.iterator]();
for (const a of iterator3) console.log(a);
for (const a of map) console.log(a);
for (const a of map.entries()) console.log(a);
for (const a of map.keys()) console.log(a);
for (const a of map.values()) console.log(a);
사용자 정의 iterable 프로토콜
- 3, 2, 1을 조회하는 iterable 프로토콜을 직접 정의하기
- iterator의 next()함수를 이용해서 진행한 후에 순회를 할 수도 있다.
let iterable = {
[Symbol.iterator]() {
let i = 3;
return {
next() {
return i === 0 ? { done : true} : { values : i--, done : false }
},
[Symbol.iterator]() { return this; } // well-formed iterator의 조건
}
}
};
let iterator = iterable[Symbol.iterator]();
for (const a of iterator) console.log(a);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
- iterator가 자기 자신을 반환하는 Symbol.iterator를 가지고 있을 때 이게 잘 정의된 well-formed iterator라고 할 수 있다.
let arr = [4, 5, 6];
let iterator = arr[Symbol.iterator]();
if (iterator[Symbol.iterator]() === iterator) console.log('well-formed iterator');
전개 연산자
- 전개 연산자로도 iterable 순환조회가 가능하다.
- 중간에 arr[Symbol.iterator] = null; 을 추가하면 arr은 iterator가 아니므로 순환조회할 수 없다고 에러가 뜬다.
const arr = [4, 5, 6, 7];
const set = new Set([1, 2, 3]);
const map = new Map([['a', 1], ['b', 2], ['c', 3]]);
console.log(...arr);
console.log(...set);
console.log(...map);
console.log(...map.keys());
console.log(...map.values());
console.log(...arr, ...set, ...map.values(), ...[10, 11]);
/*
4 5 6 7
1 2 3
[ 'a', 1 ] [ 'b', 2 ] [ 'c', 3 ]
a b c
1 2 3
4 5 6 7 1 2 3 1 2 3 10 11
*/
generator
- generator : iterator이자 iterable을 반환하는 함수
- generator가 반환하는 iterator는 well-formed iterator이다.
- generator는 순회할 값을 문장으로 표현하는 것이라고도 할 수 있다.
- javascript에선 어떠한 값이든 그게 iterable이라면 뭐든 순회할 수 있다.
- 그런데 generator는 문장을 통해서 순회할 수 있는 값을 만들 수 있다.
- 따라서 generator는 어떠한 상태는 어떠한 값이든 순회할 수 있게 만들어 버린다.
function *gen() {
yield 1;
if (false) yield 2;
yield 3;
return 100;
}
let iterator = gen(); // generator는 iterator를 반환한다.
console.log(iterator[Symbol.iterator]() === iterator);
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
console.log(iterator.next());
for (const a of gen()) console.log(a);
/*
true
{ value: 1, done: false }
{ value: 3, done: false }
{ value: 100, done: true }
{ value: undefined, done: true }
1
3
*/
odds - 홀수를 순회하는 제네레이터 만들기
function *infinite(i = 0) {
while (true) yield i++;
}
function *limit(n, iter) {
for (const a of iter) {
yield a;
if (a === n) return;
}
}
function *odds(n) {
for (const a of limit(n, infinite(1))) {
if (a % 2) yield a;
}
}
let iteraotr = odds(10);
console.log(iteraotr.next());
console.log(iteraotr.next());
console.log(iteraotr.next());
console.log(iteraotr.next());
console.log(iteraotr.next());
console.log(iteraotr.next());
/*
{ value: 1, done: false }
{ value: 3, done: false }
{ value: 5, done: false }
{ value: 7, done: false }
{ value: 9, done: false }
{ value: undefined, done: true }
*/
for…of, 전개 연산자, 구조 분해, 나머지 연산자를 이용한 순환구조
console.log(...odds(14));
console.log(...odds(14), ...odds(20));
const [head, ...tail] = odds(10);
console.log(head);
console.log(tail);
const [a, b, ...rest] = odds(20);
console.log(a);
console.log(b);
console.log(rest);
/*
1 3 5 7 9 11 13
1 3 5 7 9 11 13 1 3 5 7 9 11 13 15 17 19
1
[ 3, 5, 7, 9 ]
1
3
[ 5, 7, 9, 11, 13, 15, 17, 19 ]
*/
댓글남기기