본문으로 바로가기

Array.prototype.map보다 개선된 Map

 

함수형 프로그래밍을 위해선 이터러블 프로토콜을 따르는 객체들을 다루고, 이를 수용할 수 있게 만드는 것이 중요하다.

대표적으로,  유사 배열은 배열이 아니기 때문에 Array.prototype.map 메서드를 사용할 수 없었다.

별도의 배열에 담아두는 작업을 해왔지만, 함수형 프로그래밍을 공부하니, 별도의 map 함수를 만들어두는 것이 좀 더 많은 경우 활용할 수 있어서 좋다.

iterable에 속하는 것으로, String, Array, 유사 배열, TypedArray, Map, Set, 그리고 유저가 직접 정의한 iterable가 속한다.
https://darrengwon.tistory.com/1407

 

 유사 배열은 iterable이기 때문에, 아래와 같은 map 함수를 별도로 만들어 iteration할 수 있다.

const allDom = document.querySelectorAll('*'); // NodeList는 유사 배열

// 기존 Array.prototype.map보다 더 넓은 범위의 map 동작 처리 가능
const map = (f, iter) => {
  let names = [];
  for (const i of iter) {
    names.push(f(i));
  }
  return names;
};

// allDom의 nodeName만 담은 배열 반환
console.log(map(i => i.nodeName, allDom));

 

generator를 통해 반환된 iterator도 map으로 순환 가능하구요. iterable인 map, set 다 사용 가능합니다.

function* gen() {
  yield 1;
  yield 2;
  yield 3;
}

const iter = gen();

console.log(map(i => i + 2, iter));
const mapInstance = new Map([
  ['a', 1],
  ['b', 2],
]);

console.log(map(([key, _]) => key, mapInstance));

 

 

Array.prototype.filter보다 개선된 filter

 

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

console.log(filter(i => i.price <= 20000, products));

 

 

Array.prototype.reduce보다 개선된 reduce

 

export const reduce = (reducer, startAcc, target) => {
  let acc = startAcc;
  for (const cur of target) {
    acc = reducer(acc, cur);
  }
  return acc;
};


console.log(reduce((acc, cur) => acc + cur, 0, [1, 2, 3, 4, 5]));

 

Array.prototype.reduce는 초반 acc을 생략하면 Array의 첫 원소를 첫 acc로 설정하는데 이를 아래처럼 작성할 수 있긴하나, Array의 내부 원소가 객체 형태일 경우에는 잘못된 값을 반환한다.

물론 정의 부분에서 들어온 cur의 타입을 체킹한 다음, 객체의 특정 키만 가져오는 방법이 있긴 하겠으나, 그러면 간결함이 부족해지고, 인자가 하나 더 늘어나야하는데 이럴 바엔 그냥 초기 acc 값을 설정해주는 것이 더 효율적이다.

const products = [
  { name: '핫도그', price: 15000 },
  { name: '파인애플', price: 20000 },
];

const a = products.reduce((acc, cur) => acc + cur.price, 0);
console.log(a); // 35000

const b = products.reduce((acc, cur) => acc + cur.price);
console.log(b); // [object Object]20000150003000025000

 

이런 기존 reduce를 흉내내어서 아래처럼 작성할 수 있긴 합니다만, 그냥 첫 acc값을 제대로 할당하는 버릇을 들이는 게 더 좋아보입니다

export const reduce = (reducer, startAcc, target) => {
  // startAcc가 주어지지 않았을 때 target의 첫 인자를 startAcc로 설정
  if (!target) {
    target = startAcc[Symbol.iterator]();
    startAcc = target.next().value;
  }
  let acc = startAcc;
  for (const cur of target) {
    acc = reducer(acc, cur);
  }
  return acc;
};

 

 

Conclusion

 

그래서 결론적으론 다음과 같이 chaining 형식으로 사용할 수 있다.

filter(p => p.price > 20000, products)
    .map(p => p.price, products)
    .reduce((acc, cur) => acc + cur, 0, products)

 


darren, dev blog
블로그 이미지 DarrenKwonDev 님의 블로그
VISITOR 오늘 / 전체