[JS/Node] - 비동기 : Underbar 라이브러리

김도영·2022년 5월 20일
0
post-thumbnail
post-custom-banner

Underbar 라이브러리

과거에는 배열 메소드가 브라우저에서 자체적으로 지원되지 않던 시기가 있었다. 지금은 개발자들이 보다 나은 방법으로 배열이나 객체를 다루기 위한 라이브러리, 즉 Underbar 라이브러리를 만들었다. Underbar의 모티브가 되는 라이브러리는 underscore.js, lodahs 등이 있다.
이 중에서 Underscore.js 라이브러리의 collection을 다루는 여러 함수를 실습해보려고 한다.

*사용 가능한 내장 메소드 : pop, push, shift, sort

_.identity

전달인자(argument)가 무엇이든, 그대로 리턴한다.

.identity = function (val) {
  return val;
};

_.slice

배열의 start 인덱스부터 end 인덱스 이전까지의 요소를 얕은 복사(shallow copy)하여 새로운 배열을 리턴한다. _.slcie는 변수를 선언할 경우, 콤마(,)를 이용해 선언할 수 있다. 이 때, 콤마로 연결된 변수들은 모두 동일한 선언 키워드(let, const)가 적용된다.

_.slice = function (arr, start, end) {
  let _start = start || 0, 
  // `start`가 undefined인 경우, slice는 0부터 동작
    _end = end;

  // 입력받은 인덱스가 음수일 경우, 마지막 인덱스부터 매칭
  // ex) -1 => arr.length - 1, -2 => arr.length - 2)
  // 입력받은 인덱스는 0 이상이어야 한다.
  if (start < 0) _start = Math.max(0, arr.length + start);
  if (end < 0) _end = Math.max(0, arr.length + end);

  // `end`가 생략될 경우(undefined), slice는 마지막 인덱스까지 동작
  // `end`가 배열의 범위를 벗어날 경우, slice는 마지막 인덱스까지 동작
  if (_end === undefined || _end > arr.length) _end = arr.length;

  let result = [];
  // `start`가 배열의 범위를 벗어날 경우, 빈 배열을 리턴
  for (let i = _start; i < _end; i++) {
    result.push(arr[i]);
  }
  return result;
};

_.take

  • _.take는 배열의 처음 n개의 element를 담은 새로운 배열을 리턴한다.
  • n이 undefined이거나 음수인 경우, 빈 배열을 리턴합니다.
  • n이 배열의 길이를 벗어날 경우, 전체 배열을 shallow copy한 새로운 배열을 리턴한다.
  • _.take = function (arr, n) {
      let result = [];
      
      // n이 배열의 길이를 벗어날 경우, 전체 배열을 shallow copy한 새로운 배열을 리턴
      if ( n >= arr.length) {
        return arr;
      }
      
      for (let i = 0; i < n; i++ ) {
        if ( n === undefined || n < 0 ) { // n이 undefined이거나 음수인 경우, 빈 배열을 리턴
          return result;
        }
        else {
          result.push(arr[i]); // n이 배열의 길이 안에 있으면 arr[i]를 추가
        }
      }
      return result;
    };

    _.drop

  • _.drop은 _.take와는 반대로, 처음 n개의 element를 제외한 새로운 배열을 리턴한다.
  • n이 undefined이거나 음수인 경우, 전체 배열을 shallow copy한 새로운 배열을 리턴한다.
  • n이 배열의 길이를 벗어날 경우, 빈 배열을 리턴한다.
  • _.drop = function (arr, n) {
      let result = [];
      
      // n이 배열의 길이를 벗어날 경우, 빈 배열을 리턴
      if ( n >= arr.length) {
        return result;
      }
      
      for (let i = 0; i < arr.length; i++ ) {
        if ( n === undefined || n < 0 ) { // n이 undefined이거나 음수인 경우, 빈 배열을 리턴
          result.push(arr[i]);
        }
        else if ( i >= n ) {
          result.push(arr[i]); // n이 배열의 길이 안에 있으면 arr[i]를 추가
        }
      }
    
      return result;
    };

    _.last

  • _.last는 배열의 마지막 n개의 element를 담은 새로운 배열을 리턴한다.
  • n이 undefined이거나 음수인 경우, 배열의 마지막 요소만을 담은 배열을 리턴한다.
  • n이 배열의 길이를 벗어날 경우, 전체 배열을 shallow copy한 새로운 배열을 리턴한다.
  • _.last = function (arr, n) {
      if ( n === undefined || n < 0 ) {
        return _.slice(arr, -1); // n이 undefined이거나 음수인 경우, 배열의 마지막 요소만을 담은 배열을 리턴
      }
      else if ( n > arr.length ) {
        return _.take(arr, n); // n이 배열의 길이를 벗어날 경우, 전체 배열을 shallow copy한 새로운 배열을 리턴
      }
      else if ( n === 0 ) {
        return []; // n이 0일 경우 빈 배열을 리턴
      }
      else {
        return _.slice(arr, -n);
      }
    };

    _.each

    .each는 collection의 각 데이터에 반복적인 작업을 수행한다. collection(배열 혹은 객체)과 함수 iteratee(반복되는 작업)를 인자로 전달받아 (iteratee는 함수의 인자로 전달되는 함수이므로 callback 함수) collection의 데이터(element 또는 property)를 순회하면서 iteratee에 각 데이터를 인자로 전달하여 실행한다. .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);
        }
      }
    };

    _.indexof

  • _.indexOf는 target으로 전달되는 값이 arr의 요소인 경우, 배열에서의 위치(index)를 리턴한다.
  • 그렇지 않은 경우, -1을 리턴한다.
  • target이 중복해서 존재하는 경우, 가장 낮은 index를 리턴한다.
  • _.indexOf = function (arr, target) {
      let result = -1;
    
      _.each(arr, function (item, index) {
        if (item === target && result === -1) {
          result = index;
        }
      });
    
      return result;
    };

    _.filter

  • _.filter는 test 함수를 통과하는 모든 요소를 담은 새로운 배열을 리턴한다.
  • test(element)의 결과(return 값)가 truthy일 경우, 통과
  • test 함수는 각 요소에 반복 적용
  • _.filter = function (arr, test) {
      let result = [];
    
      _.each(arr, function(el) {
        if( test(el) ) {
          result.push(el) // test(el)를 통과하고 각 요소에 반복적용
        }
      });
      return result;
    };

    _.reject

    .reject는 .filter와 정반대로 test 함수를 통과하지 않는 모든 요소를 담은 새로운 배열을 리턴한다.

    _.reject = function (arr, test) {
      let result = [];
    
      _.each(arr, function(el) {
        if( !test(el) ) {
          result.push(el) // test(el)를 통과하고 각 요소에 반복적용
        }
      });
      return result;
    };

    _.uniq

  • _.uniq는 주어진 배열의 요소가 중복되지 않도록 새로운 배열을 리턴한다.
  • 중복 여부의 판단은 엄격한 동치 연산(strict equality, ===)을 사용해야 한다.
  • 입력으로 전달되는 배열의 요소는 모두 primitive value라고 가정한다.
  • _.uniq = function (arr) {
      let result = [];
    
      for (let i = 0; i < arr.length; i++) {
        if (_.indexOf(result, arr[i]) === -1 ) { // _.indexof 가 존재하지 않으면 -1이기 때문에
          result.push(arr[i]);
        }
      }
      return result;
    };

    _.map

  • _.map은 iteratee(반복되는 작업)를 배열의 각 요소에 적용(apply)한 결과를 담은 새로운 배열을 리턴한다.
  • _.map은 배열의 각 요소를 다른 것(iteratee의 결과)으로 매핑(mapping)한다.
  • _.map = function (arr, iteratee) {
      // _.each 함수와 비슷하게 동작하지만, 각 요소에 iteratee를 적용한 결과를 리턴한다.
      let result = [];
    
      _.each(arr, function(el) {
          result.push(iteratee(el)); 
      });
    
      return result;
    };

    _.pluck

    _.pluck은 객체 또는 배열을 요소로 갖는 배열과 각 요소에서 찾고자 하는 key 또는 index를 입력받아 각 요소의 해당 값 또는 요소만을 추출하여 새로운 배열에 저장하고, 최종적으로 새로운 배열을 리턴한다.

    _.pluck = function (arr, keyOrIdx) {
      // _.each를 사용해 구현
      let result = [];
      
      _.each(arr, function (item) {
      	result.push(item[keyOrIdx]);
      });
      return result;
    }
    
    _.pluck = function (arr, keyOrIdx) {
      // _.map를 사용해 구현
      let result = [];
    
      _.map(arr, function (item) {
        result.push(item[keyOrIdx]);
      });
      return result;
    }

    _.reduce

    .reduce는 배열을 순회하며 각 요소에 iteratee 함수를 적용하고, 그 결과값을 계속해서 누적(accumulate)하고, 최종적으로 누적된 결과값을 리턴한다. .reduce는 배열이라는 다수의 정보가 하나의 값으로 축소(응축, 환원, reduction)되기 때문에 reduce라는 이름이 붙게 된 것이다. .reduce는 위에서 구현한 많은 함수처럼, 입력으로 배열과 각 요소에 반복할 작업(iteratee)을 전달받습니다. iteratee는 차례대로 데이터(element 또는 value), 접근자(index 또는 key), collection을 다룰 수 있어야 한다. 배열 arr을 입력받을 경우, iteratee(ele, idx, arr) .reduce는 반복해서 값을 누적하므로 이 누적되는 값을 관리해야 한다.
    여기까지 정리하면 다음과 같다.

    _.reduce(arr, iteratee)
    iteratee(acc, ele, idx, arr)

    여기서 중요한것은, '누적값은 어디서부터 시작하는가'라는 것인데, 이를 해결하는 방법은 초기 값을 직접 설정하거나 자동으로 설정하는 것이다. _.reduce는 세 번째 인자로 초기 값을 전달받을 수 있기 때문에,

  • 세 번째 인자로 초기 값이 전달되는 경우, 그 값을 누적값의 기초(acc)로 하여 배열의 '첫 번째' 요소부터 반복 작업이 수행
  • 반면 초기 값이 전달되지 않은 경우, 배열의 첫 번째 요소를 누적값의 출발로 하여 배열의 '두 번째' 요소부터 반복 작업이 수행한다.
  • 따라서 최종적인 형태는

    _.reduce(arr, iteratee, initVal)
    iteratee(acc, ele, idx, arr)
    _.reduce = function (arr, iteratee, initVal) {
      let result = initVal;
      
      _.each(arr, function(el, idx) {
        if ( result === undefined ) {
          result = el; // 초기 값이 주어지지 않았다면, 배열의 첫 요소가 result가 된다.
        }
        else {
          result = iteratee(result, el, idx, arr);
        }
      });
      return result;
    };
    profile
    Blockchain Developer
    post-custom-banner

    0개의 댓글