본문으로 바로가기

기본적으로 우리가 알고 있는 자바스크립트의 map 함수의 경우 Array를 상속받은 객체만 사용할 수 있는 함수이다. 즉 일반적으로 사용하는 Array의 경우에는 prototype으로 map 함수가 구현이 돼있어 map 함수를 사용할 수 있지만,

document.querySelectorAll("*");

위와 같은 경우에는 이터러블 객체임에도 불구하고 Array를 상속받은 객체는 아니므로 map 함수를 사용할 수가 없다. 그러니 이터러블 프로토콜을 만족하는 객체라면 어떤 객체든 사용할 수 있는 map 함수를 한 번 만들어 보자.

const arr = [1, 2, 3, 4, 5];
const nodes = document.querySelectorAll("*");

const map = (f, iter) => {
    let res = [];
    for(const a of iter) {
        res.push(f(a));
    }
    return res;
}

console.log(map(e => e * 3, arr));    // [3, 6, 9, 12, 15]
console.log(map(e => e.nodeName, nodes));    // ["HTML", "HEAD", "META", "META", "META", "TITLE", "BODY"]

이터러블 객체를 순회하는 for of 문을 이용해 이렇게 만들어주면, Generator 함수를 포함해 이터러블 프로토콜을 만족하는 모든 객체를 반복해 반환할 수 있다. filter 함수도 마찬가지다 filter 함수도 Array.prototype에 구현된 함수이므로, 모든 이터러블 객체가 사용할 수 있게 만들려면 아래와 같이 함수를 따로 만들어 주면 된다.

const arr = [1, 2, 3, 4, 5];
const nodes = document.querySelectorAll("*");

const filter = (f, iter) => {
    let res = [];
    for(const a of iter) {
        if(f(a)) {
            res.push(a);
        }
    }
    return res;
}

console.log(filter(e => e > 2, arr));    // [3, 4, 5]
console.log(filter(e => e.nodeName === "META", nodes));    // [meta, meta, meta]

reduce 함수도 똑같다. 다만 reduce 함수는 시작 value를 주지 않았을 경우도 감안해서 함수를 만들어야 한다. 때문에 조건문을 걸어 만약 시작 value인 acc가 없을 경우(세 번째 파라미터가 없을 경우)에는 이터러블 값의 첫 번째 값을 next()로 뽑아 acc로 해주었다.

const arr = [1, 2, 3, 4, 5];
const add = (a, b) => a + b;

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;
}

console.log(reduce(add, 0, arr));    // 15

함수형 프로그래밍과 ES6+ / 유인동님