자바스크립트 내장 Array.prototype.map은 배열에만 동작한다. document.querySelectorAll('*').map은 undefined다. 이터러블 프로토콜을 따르는 for...of 기반으로 구현하면 어떤 이터러블에도 동작하는 다형성을 얻을 수 있다.
const map = (f, iter) => {
let res = [];
for (const a of iter) {
res.push(f(a));
}
return res;
};
내장 map이 안 되는 NodeList도 된다.
log(map(el => el.nodeName, document.querySelectorAll('*')));
// ['HTML', 'HEAD', ...]
제너레이터도 된다.
function* gen() { yield 2; yield 4; }
log(map(a => a * a, gen())); // [4, 16]
Map 자료형도 된다.
const m = new Map([['a', 10], ['b', 20]]);
log(new Map(map(([k, a]) => [k, a * 2], m)));
// Map { 'a' => 20, 'b' => 40 }
const filter = (f, iter) => {
let res = [];
for (const a of iter) {
if (f(a)) res.push(a);
}
return res;
};
log(...filter(p => p.price < 20000, products));
log(filter(n => n % 2, [1, 2, 3, 4])); // [1, 3]
누산기를 가지고 이터러블을 하나의 값으로 접는 함수다. 초기값이 없으면 첫 번째 값을 초기값으로 쓴다.
const reduce = (f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
};
const add = (a, b) => a + b;
log(reduce(add, 0, [1, 2, 3, 4, 5])); // 15
log(reduce(add, [1, 2, 3, 4, 5])); // 15
상품 목록에서 가격 합산도 동일한 구조다.
log(reduce(
(total, product) => total + product.price,
0,
products
));
세 함수를 중첩해서 쓸 수 있다.
log(
reduce(
add,
map(p => p.price,
filter(p => p.price < 20000, products))));
이 중첩 구조를 읽기 좋게 펴는 게 다음 섹션에서 다룰 go와 pipe다.