[underscore js] 함수 구현

const_yang·2021년 10월 11일
1

underscore js는 배열과 객체를 다루는 함수형 프로그래밍 라이브러리이다. Javascript에 내장되어 있는 배열, 객체 메소드가 존재하기 전에 해당 라이브러리를 사용했다고 한다. 지금도 여전히 핫(?)한 라이브러리이다.

이전에 고차함수를 공부하며 배열과 객체의 메소드와 관련한 공부를 할 때 배웠던 메소드를 비롯해 다양한 프로그래밍이 가능하도록 하는 함수들이 많았다.

그 중 흥미로운 몇 가지만 digging 해보자.

1) .each

_.each = function (collection, iteratee) {
  if(Array.isArray(collection)){
    for(let i = 0; i <collection.length; i++ ){
      iteratee(collection[i], i, collection)
    };
  } else if (typeof collection === 'object') {
    for (let key in collection) {
      iteratee(collection[key], key, collection)
    }
  }
};

해설:

배열 메소드 forEach를 닮았다. 각 요소에 함수를 적용한다. 배열과 객체 모두 반복하여 (요소, 인덱스, 배열) 또는 (값, 키, 객체)와 같은 인자를 전달할 수 있다.

2) _.filter

_.filter = function (arr, test) {
  let result = [];
  
  _.each(arr, function (ele) {
    if (test(ele)) {
      result.push(ele)
    }
  })
  return result;
};

해설:

filter메소드 내에 새로운 함수(콜백함수)를 인자로 넣은 것과 같이 _.filter에는 배열과 함수(test)가 들어간다. 배열 메소드 filter와 똑같은 기능을 한다.

3) _.map

_.map = function (arr, iteratee) {
    let result =[];
    _.each(arr, function (item) {
          result.push(iteratee(item))         
      });
      return result;
    };

해설:

역시 배열 메소드 map과 기능이 같다. 배열의 각 요소를 어떤 반복되는 callback 함수(iteratee)를 적용한 새로운 배열을 반환한다.

4) _.reduce

_.reduce = function (arr, iteratee, initVal) {

  let accumulator = initVal;
  
  _.each(arr, function (item, idx, src){
    if (initVal === undefined && idx ===0) {
      accumulator = item
    } else {
      accumulator = iteratee(accumulator, item, idx, src)
    }
  })
  return accumulator;
}

해설:

reduce의 경우 초기값 유무에 따라 accumulator가 다르다. 초기값이 있는 경우 초기값이 accumulator가 되어 첫 번째 요소부터 진행하며, 초기값이 없는 경우 배열의 첫 번째 요소가 accumulator가 되어 진행된다.

5) _.once

_.once = function (func) {
  let result;
  let alreadyCalled = false;

  return function (...args) {
    if (!alreadyCalled) {
      alreadyCalled = true;
      result = func(...args);
    }
    return result;
  };
};

해설:

특정 조건에만 실행되는 함수를 리턴하도록 한다. 함수는 리턴되면서 해당 상태를 변경하여 _.once 함수를 여러 번 실행해도 값에 변경이 없다.

예시:

function plus (a, b) {
  return a + b
}

const add = once(plus)

add(1, 2) // 3
add(3, 6) // 3

6) _.intersection

설명:

_.intersection은 여러 개의 배열을 입력받아, 교집합 배열을 리턴합니다.
교집합 배열은 모든 배열에 공통으로 등장하는 요소들만을 요소로 갖는 배열입니다.
교집합 배열의 요소들은 첫 번째 입력인 배열을 기준으로 합니다.

_.intersection = function (base, ...rest) {
  let result = [];
  _.each(base, function (bItem) {
    const intersected = _.every(rest, function (arr) {
      return _.includes(arr, bItem)})
      // _.every 함수의 경우 첫 번째 인사로 들어온 각 요소에 iteratee를 모두 적용하고 모든 요소값이 true값이 나오면 최종 true를 리턴한다.
    if (intersected) {
      result.push(bItem);
    }
  });
  return result;
}

해설:

1) 전개구문을 사용하여 매개변수를 표현

교집합이기 때문에 모든 배열에서 등장하는 요소를 찾아야 한다. 첫 번째 배열(base)을 기준으로 _.each를 사용하여 나머지 배열을 모두 확인하면 된다.

2) 각 요소(bItem)를 나머지 배열(...rest)에서 확인

_.every 첫 번째에 rest를 넣고 (나머지 배열을 담은 배열), rest의 각 배열 안에 bItem이 있는지 확인하는 _.includes를 사용했다. 그러면 모든 배열에서 true 값을 가지면 intersected는 true가 될 것 이다.

const intersected = _.every(rest, function (arr) {
      return _.includes(arr, bItem);
    });

7)_.difference

설명:

_.difference는 여러 개의 배열을 입력받아, 차집합 배열을 리턴합니다.
차집합 배열은 첫 번째 배열에서 차례대로 다음 배열들의 요소들을 제외한 배열입니다.
차집합 배열의 요소들은 첫 번째 입력인 배열을 기준으로 합니다.
차집합이 없는 경우 빈 배열을 리턴합니다.

_.difference = function (base, ...rest) {
  let result = []
  _.each(base, function (bItem) {
    const existSomeWhere = _.some(rest, function (arr) {
      return _includes(arr, bItem)})
    // _.some 함수의 경우 첫 번째 인사로 들어온 각 요소에 iteratee를 모두 적용하고 한 요소라도 true값이 나오면 최종 true를 리턴한다.
    if (!existSomeWhere) {
      result.push(bItem);
    }
  })
  return result;
}

해설:

첫 번째 배열 내 요소를 기준으로 차집합을 찾는 함수이다. _.each 함수를 활용하여 첫 번째 배열 각 요소를 돌면서 iteratee 함수를 실행한다.
_.some 함수를 통해 나머지 배열 각 배열 내에 해당 값이 있는 지 확인해 보고 한 배열에서라도 위 첫 번째 배열의 요소 값이 확인되면 true를 리턴하도록 한다. 해당 값이 false라는 것은 나머지 배열 어느 배열에서도 확인이 되지 않았다는 뜻이다. 해당 요소 값을 result에 넣어 준다.

8) (가장 어려웠던)_.sortBy

설명:

세 번째 인자인 order는 정렬의 방향을 나타냅니다. 생략되거나 1을 입력받은 경우 오름차순, -1을 입력받은 경우 내림차순으로 정렬합니다.

_.sortBy = function (arr, transform, order) {
  order = order || 1;
  transform = transform || _.identity;
  const arrCloned = _.slice(arr);
  return arrCloned.sort(function (a, b) {
    if (transform(a) < transform(b)) {
      return -1 * order;
    }
    return order;
  });
};

해설:

배열 메소드 sort의 구현 방식을 이해해야 한다.

// 오름차순
if (a > b) {
  retun 1 // 앞 수가 뒷 수보다 크면 둘이 바꿔 (1)
}
if (a < b) {
  return -1 // 뒷 수가 앞 수보다 크면 바꾸지마 (-1)
}  
// 내림차순
if (a > b) {
  return -1 // 앞 수가 뒷 수보다 크면 바꾸지마 (-1)
}
if (a < b) {
  return 1 // 뒷 수가 앞 수보다 크면 바꿔 (1)
}

order 인자로 주어지는 값에 따라 sort 메소드의 return 값을 조절하면 우리가 원하는 순서대로 요소를 정렬할 수 있다.

0개의 댓글