210621. Today I Learned(TIL) : 고차함수(Underbar)

syong·2021년 6월 21일
0

TIL

목록 보기
15/32

고차함수를 익히기 위한 훈련 중 오늘은 언더바(underbar) 메소드 구현 연습을 해보았다. 언더바 메소드 구현이란, 원래 자바스크립트에 내장되어 있는 메소드들을 새롭게 메소드 이름 앞에 언더바(_)를 붙여서 똑같은 기능으로 구현해 보는 연습이다.
오늘은 언더바 메소드 중 중요한 메소드 몇 가지만 리뷰해보려 한다.

_.each
each는 자바스크립트 내장 메소드와 비교하자면 forEach와 가장 유사하게 동작한다. 오늘 구현해 본 _.each 함수는 정확히 말하면 인자로 넘겨받은 배열의 요소, 인덱스 그리고 입력받은 배열 그 자체 또는 객체의 속성, 값 그리고 입력받은 객체 그 자체를 인자로 넘겨받은 콜백함수의 인자로 넘겨주는 역할을 한다. 따라서 내부 구현은 collection이 배열일 때와 객체일 때를 나누어 코드를 작성하는 것이 좋다.

// _.each는 명시적으로 어떤 값을 리턴하지 않는다.
_.each = function (collection, iteratee) {
  if (Array.isArray(collection)) {
    for (let i = 0; i < collection.length; i++) {
      iteratee(collection[i], i, collection);
    }
  } else {
    for (let key in collection) {
      iteratee(collection[key], key, collection);
    }
  }
};

each 함수는 명시적인 리턴값이 없기 때문에 each 함수 자체를 리턴시켜도 아무것도 리턴되지 않는다는 점을 주의하자.

_.filter
언더바 filter함수는 자바스크립트 내장 메소드 중 filter 메소드와 동일하게 동작한다. filter도 each 함수처럼 배열 또는 객체의 정보를 콜백함수의 인자로 넘겨주어야 하기 때문에 filter 구현에는 each도 필요하다.

// test(element)의 결과(return 값)가 truthy일 경우, 통과
_.filter = function (arr, test) {
  let result = [];

  _.each(arr, function (item) {
    if (test(item)) {
      result.push(item);
    }
  });

  return result;
};

_.map
map은 each와 매우 유사하게 동작하지만 each와 다르게 매핑(mapping)된 새로운 배열을 명시적으로 리턴한다는 점이 다르다.

// _.map은 배열의 각 요소를 다른 것(iteratee의 결과)으로 매핑(mapping)한다.
_.map = function (arr, iteratee) {
  let result = [];

  _.each(arr, function (item) {
    result.push(iteratee(item));
  });

  return result;
};

_.reduce
reduce 함수는 매우 유용하지만 고차함수 메소드 중에서 진입장벽이 높은 편에 속하는 메소드이다. 누적값과 초기값의 개념이 처음 배우는 사람들에게 낯설게 느껴질 수 있다. 그렇기 때문에 더욱 내부 구현을 직접 해 보는 것이 좋다. 함수가 정확히 어떻게 내부적으로 동작하는지 건물에 비유하면 설계 도면을 보게 되는 것이기 때문이다. reduce 함수는 내가 작성한 코드와 레퍼런스 코드에 약간의 차이가 있는데 이 차이점을 짚어보며 리뷰하려 한다.

내 코드

_.reduce = function (arr, iteratee, initVal) {
  if(initVal === undefined){ // 초기값이 정해져 있지 않을 때
    initVal = arr[0] // 배열의 첫 번째 요소를 초기값으로 임의 지정한다.
    _.each(_.slice(arr, 1), function(el, idx, arr){
      initVal = iteratee(initVal, el, idx, arr)
    })
  }
  else{
    _.each(arr, function(el, idx, arr){ // 초기값이 있을 때
      initVal = iteratee(initVal, el, idx, arr)
    })
  }
  return initVal;
};

내가 작성한 코드에서는 동일하게 반복되는 코드가 존재한다. 필요한 반복이라면 몰라도 위의 코드에서는 불필요하게 코드가 반복되고 있기 때문에 더 효율적인 코드를 작성하기 위해서는 수정이 필요하다. 또한 initVal이 초기값인 동시에 누적값의 역할을 한다는 것을 한 눈에 알아보기 힘들다. initVal에 콜백함수를 적용한 각 요소들을 누적해준다는 것을 코드를 읽어내려가다 보면 알아챌 수는 있지만 명시적이진 않다. 다음 레퍼런스 코드는 이런 단점들을 보완한 코드이다.

레퍼런스 코드

_.reduce = function (arr, iteratee, initVal) {
  let accumulator = initVal; // 처음부터 초기값이 누적값 역할을 한다는 것을 명시함

  _.each(arr, function (item, idx, src) {
    if (initVal === undefined && idx === 0) { // 초기값이 정해져 있지 않고, 첫 순회(idx === 0)라면 초기값(누적값)을 현재 요소로 지정해 준다.
      accumulator = item;
    } else { // 그 외의 경우 => 위의 조건식을 통해 초기값이 원래 주어지지 않았던 상태에서 초기값이 지정된 상태로 바뀌었으며, 처음부터 초기값이 주어진 경우라면 위의 조건식을 통과하고 바로 else문으로 진입하게 될 것이므로 불필요한 반복을 줄여 하나의 로직으로 합쳐놓았다.
      accumulator = iteratee(accumulator, item, idx, src);
    }
  });

  return accumulator;
};

조금만 생각하면 훨씬 깔끔한 코드를 작성할 수 있다. 코드를 많이 작성해보면서 앞으로 레퍼런스 코드처럼 효율적인 코드를 작성할 수 있도록 노력해야겠다.

0개의 댓글