본문 바로가기

개발/ES6

Javascript ES6 - map, filter, reduce 함수 내부 동작 구현하기

1. Map

function myMap(list, func) {
  let result = [];
  for (let i = 0; i < list.length; i++) {
    result.push(func(list[i]));
  }
  return result;
}

const list = [1, 2, 3, 4];
const example = list.map((x) => (x % 2 === 0 ? true : false));

console.log(example);
console.log(myMap(list, (x) => (x % 2 === 0 ? true : false)));

map 함수 내부를 구현해보았다. 어려운 것은 아니고, 따로 myMap을 만들어서 내부적으로 어떻게 동작하는지 짐작하여 구현해보았다.

여기에서 중요한 포인트는 Javascript에서는 일급 객체인 함수를 매개 변수로, 인자로, 리턴값으로 사용할 수 있다는 것이다.

따라서 func라는 매개 변수를 두었기에 인자로 함수를 받을 수 있었다.

 


2. Filter

 

function myFilter(list, func) {
  let result = [];
  for (let i = 0; i < list.length; i++) {
    if (func(list[i])) {
      result.push(func(list[i]));
    }
  }
  return result;
}

const list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const example = list.filter((x) => x % 2 === 0);

console.log(example);
console.log(myFilter(list, (x) => x % 2 === 0));

filter는 위의 map에서 if문 1줄만 추가하면 된다. map은 list.length 길이와 return값의 length 길이가 같아야하고, 전체 원소에 적용할 수 있는 함수인데에 비해 filter는 원하는 조건의 요소만 반환 받을 수 있다. 그래서 map과 filter는 적재적소에 맞게 활용해야 한다.

 


3. reduce

 

먼저 간단하게 1부터 10까지 더하는 연산을 하는 reduce function을 사용해봅시다.

const list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

const reduceSum = list.reduce(function (acc, value) {
  console.log(`acc : ${acc} `);
  console.log(`value : ${value} `);
  return acc + value;
});

 

중간에 accumulate와 value값을 log로 찍어 작동 방식에 대해 파악해보려고 합니다. 위의 출력은 log만 찍어본 것이고, 좀 더 보기 쉽게 log를 다시 찍어보겠습니다.

const reduceSum = list.reduce(function (acc, value) {
  console.log(`${acc} + ${value} = ${acc + value}`);
  return acc + value;
});

acc값에 list 배열의 가장 첫 번째 값을 먼저 넣어두고 누산을 시작합니다. acc에 value를 더하고, 더해서 acc값에 다시 넣고 또 value와 넣는 꼴의 반복입니다. 이렇게만 보면 참 쉬운데, 배열 내에 문자열이나 객체로 연산을 하라고 하면 상당히 헷깔릴 것 같습니다.

 


 

대략 어떻게 사용하는지 알았으니 이제 reduce 함수는 내부적으로 어떻게 구현되어 있을지 유추해보고자 합니다.

const list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

/**
 * (1) Reduce 내부 구현
 */

function myReduce1(list, func, init) {
  let acc = init;

  for (let i = 1; i < list.length; i++) {
    let value = list[i];
    console.log(`[myReduce 1] ${acc} + ${value} = ${func(acc, value)}`);
    acc = func(acc, value);
  }
  return acc;
}
let myReduceResult = myReduce1(
  list,
  function (acc, value) {
    return acc + value;
  },
  list[0]
);

console.log(`myReduce1 : ${myReduceResult}`);

/**
 * (2) Reduce
 * 초깃값 설정 없음
 */

const myReduce2 = list.reduce(function (acc, value) {
  console.log(`[myReduce 2] ${acc} + ${value} = ${acc + value}`);
  return acc + value;
});

let reduceResult = myReduce2;
console.log(`myReduce2 : ${reduceResult}`);

 

오래 걸릴 일은 아닌데 초깃값 설정을 어떻게 했을지 유추하다보니 시간이 좀 걸렸습니다.

myReduce2는 reduce 그대로 사용한 것인데, 초깃값을 매개변수로 던져주지 않았습니다. 기본적으로 설정된 매개변수는 아마도 배열의 첫 번째 요소이지 않을까 싶었습니다.

 

 

초깃값을 0으로 시작하고 reduce를 돌리니 기존 reduce 함수와 log 찍히는게 다르더라구요. 그래서 기본적으로 내장된 reduce 함수는 배열 첫 번째 요소를 accumulate에 먼저 담고 시작하지 않을까 싶었습니다.