Underscore.js 고차 함수 구현하기

지은·2022년 9월 24일
1

Node.js Library

목록 보기
4/14

Underscore.js

: map, filter, invoke 등 100여개의 유용한 함수들을 제공하는 JavaScript 라이브러리
Underscore.js

collection

: 데이터(data)/자료의 모음, 대표적으로 배열과 객체

  • 배열 : 데이터들(요소, element)을 순서대로 모은 자료 구조
    • 요소의 위치(index)를 통해 데이터에 접근할 수 있다.
  • 객체 : 서로 관련있는 데이터들(속성, property)을 'key-value' 형태로 순서 없이 모은 자료 구조
    • 속성에 부여된 키(key)를 통해 데이터에 접근할 수 있다.

iteration & iteratee

우리는 주로 collection을 가지고 collection 각 데이터에 반복 작업(iteration)을 한다.
JavaScript에서는 이러한 iteration을 위해 반복문과 반복을 위한 내장 메소드를 제공한다.

  • 반복문 : for, for...of, for...in, while
  • 내장 메소드 : arr.map, arr.filter
for(let i = 0; i < 3; i++) {
  console.log(i); // 0 // 1 // 2
}

이때, console.log(i); 처럼 반복되는 작업iteratee라고 부른다.

Underscore.js 라이브러리에서 제공하는 함수 중, collection을 다루는 몇 가지 함수를 직접 구현해보자.


_.slice()

_.slice(arr, start, end) : 배열의 start부터 end 전까지의 요소를 얕게 복사(shallow copy)해 새로운 배열로 만들어 반환하는 함수

코드

_.slice = function(arr, start, end) {

  let _start = start || 0; 
  let _end = end;

  if(start < 0) _start = Math.max(0, arr.length + start); 
  if(end < 0) _end = Math.max(0, arr.length + end);
  
  if(_end === undefined || _end > arr.length) _end = arr.length;
  
  let result = [];
  for(let i = _start; i < _end; i++) {
  	result.push(arr[i]);
  }
  
  return result;
}

코드 /w 의사코드

_.slice = function(arr, start, end) {
  // start를 입력하지 않았을 경우(undefined), _start는 0부터 시작한다.
  let _start = start || 0; 
  let _end = end;

  // 만약 index에 -1을 넣었을 경우, arr.length - 1을 반환한다.
  // 만약 배열의 길이를 초과하는 index를 넣을 경우, 0을 반환한다.
  if(start < 0) {
    _start = Math.max(0, arr.length + start); 
  }
  if(end < 0) {
  	_end = Math.max(0, arr.length + end);
  }
  
  // end를 입력하지 않았을 경우(undefined), _end는 배열의 끝까지이다.
  // end가 배열의 범위를 벗어날 경우, _end는 배열의 끝까지이다.
  if(_end === undefined || _end > arr.length) {
 	_end = arr.length;
  }
  
  let result = []; // 새로운 배열을 만들어 반환한다.
  for(let i = _start; i < _end; i++) {
  	result.push(arr[i]);
  }
  
  return result;
}

_.take()

_.take(arr, n) : 배열의 처음 n개의 요소를 담은 새로운 배열을 반환하는 함수

조건

  • nundefined이거나 음수인 경우, 빈 배열을 리턴한다.
  • n이 배열의 길이를 벗어난 경우, 전체 배열 얕게 복사한 새로운 배열을 리턴한다.
_.take = function(arr, n) {
  let result = [];

  if(n === undefined || n < 0) n = 0;
  else if(n > arr.length) n = arr.length;

  for(let i = 0; i < n; i++) {
    result.push(arr[i]);
  }

  return result;
};

_.drop()

_.drop(arr, n) : _.take()와 정반대로 배열의 처음 n개의 요소를 제외한 새로운 배열을 반환하는 함수

조건

  • nundefined거나 음수인 경우, 전체 배열을 얕게 복사한 새로운 배열을 리턴한다.
  • n이 배열의 길이를 벗어날 경우, 빈 배열을 리턴한다.
_.drop = function(arr, n) {
   let result = [];

  if(n === undefined || n < 0) n = 0;
  else if(n > arr.length) n = arr.length;

  for(let i = n; i < arr.length; i++) {
    result.push(arr[i]);
  }

  return result;
};

_.last()

_.last(arr, n) : 배열의 마지막 n개의 요소를 담은 새로운 배열을 반환하는 함수

조건

  • nundefined거나 음수인 경우, 배열의 마지막 요소만 담은 배열을 리턴한다.
  • n이 배열의 길이를 벗어날 경우, 전체 배열을 얕게 복사한 새로운 배열을 리턴한다.
  • _.drop() 함수를 활용한다.
_.last = function(arr, n) {
  if(n === undefined || n < 0) n = 1;
  
  return _.drop(arr, arr.length - n);
};

forEach()

arr.forEach(callback) : 배열을 순회하며 배열의 각 요소에 주어진 함수를 실행하는 메소드

callback(currentValue, index, array)

  • forEach() 메소드의 매개변수인 콜백 함수는 다음 세 가지 매개변수와 함께 호출된다.
    • currentValue : 요소 값
    • index : 요소 인덱스
    • array : 순회 중인 배열

_.each()

_.each(collection, iteratee) : collection을 순회하며 collection의 각 데이터에 iteratee 함수를 실행하는 함수

조건

  • iteratee는 차례대로 데이터(element/value), 접근자(index/key), collection(배열/객체)을 다룰 수 있어야 한다.
    ➡️ 매개변수로 전달해야 한다.
    • 배열을 입력받을 경우, iteratee(el idx collection)
    • 객체를 입력받을 경우, iteratee(value key collection)
_.each = function(collection, iteratee) {
  // collection이 배열일 때
  if(Array.isArray(collection)) {
    for(let i = 0; i < collection.length; i++) {
      iteratee(collection[i], i, collection);
    }
  // collection이 객체일 때
  } else { 
    for(let key in collection) {
      iteratee(collection[key], key, collection);
    }
  }
};

indexOf()

arr.indexOf(searchelement) : 배열에서 지정된 요소를 찾을 수 있는 첫 번째 인덱스를 반환하고 존재하지 않으면 -1을 반환하는 메소드

_.indexOf()

_.indexOf(arr, target) : targetarr의 요소인 경우, 배열에서의 index를 반환하고, 그렇지 않으면 -1을 반환하는 함수

조건

  • target이 중복해서 존재하는 경우, 가장 낮은 index를 반환한다.
  • _.each() 함수를 활용해 배열을 순회한다.
_.indexOf = function(arr, target) {
  let result = -1;

  _.each(arr, function(el, idx) {
    if(target === el && result === -1) { // 가장 낮은 index를 반환하기 위한 장치
      result = idx;
    }
  });
  
  return result;
};

filter()

arr.filter(callback) : 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환하는 메소드

callback(element, index, array)

  • filter() 메소드의 매개변수인 콜백 함수는 다음 세 가지 매개변수와 함께 호출된다.
    • element : 요소값
    • index : 요소 인덱스
    • array : 순회되는 배열 객체

_.filter()

_.filter(arr, test) : test 함수를 통과하는 모든 요소를 담은 새로운 배열을 반환하는 함수

조건

  • test 함수의 결과값이 truthy일 경우 통과이다.
  • test 함수는 각 요소에 반복 적용된다.
_.filter = function(arr, test) {
  let result = [];

  _.each(arr, function(el, idx, collection) {
    if(test(el, idx, collection)) {
      result.push(el);
    }
  });

  return result;
};

_.reject()

_.reject(arr, test) : _.filter()와 정반대로 test 함수를 통과하지 않는 모든 요소를 담은 새로운 배열을 반환하는 함수

_.reject = function(arr, test) {
  let result = [];

  _.each(arr, function(el, idx, collection) {
    if(!test(el, idx, collection)) { // 조건문에 !만 추가
      result.push(el);
    }
  });

  return result;
};

_.uniq()

_.uniq(arr) : 주어진 배열의 요소가 중복되지 않는 새로운 배열을 반환하는 함수

조건

  • 중복 여부의 판단은 엄격한 동치 연산(strict equality, ===)을 사용한다.
  • 배열의 요소는 모두 원시 값(primitive value)라고 가정한다.
_.uniq = function(arr) {
  let result = [];

  _.each(arr, function(el, idx, collection) {
    // result 배열에 el이 없으면, 추가한다.
    if(_.indexOf(result, el) === -1) {
      result.push(el);
    }
  });

  return result;
};

map()

arr.map(callback) : 배열 내의 각각의 요소에 주어진 함수를 실행한 결과를 모아 새로운 배열을 반환하는 메소드

callback(currentValue, index, array)

  • map() 메소드의 매개변수인 콜백 함수는 다음 세 가지 매개변수와 함께 호출된다.
    • currentValue : 요소 값
    • index : 요소 인덱스
    • array : 순회 중인 배열

_.map()

_.map(arr, iteratee) : 배열의 각 요소에 iteratee 함수를 적용한 결과를 담은 새로운 배열을 반환하는 메소드

_.map = function(arr, iteratee) {
  let result = [];

  _.each(arr, function(el, index, collection) {
    result.push(iteratee(el, index, collection));
  });

  return result;
};

_.pluck()

_.pluck(arr, keyOrIdx) : 매개변수로 ① 객체 또는 배열을 요소로 갖는 배열과 ② 각 요소에서 찾고자 하는 key/index를 입력 받아,
각 요소의 특정 요소/값만을 추출해 새로운 배열로 반환하는 함수

조건

  • 최종적으로 리턴되는 새로운 배열의 길이는, 입력으로 전달되는 배열의 길이와 같아야 한다.
  • 따라서, 찾고자 하는 key/index가 없는 요소의 경우, 추출 결과는 undefined이다.
  • _.map() 함수를 활용한다.
  let result = [];          // [[1, 2, 3], [4, 5, 6]]

  _.map(arr, function(el) { // el = [1, 2, 3]
    result.push(el[keyOrIdx]);
  });

  return result;

reduce()

arr.reduce(callback, initialValue) : 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환하는 메소드

callback(acc, cur, idx, arr)

  • reduce() 메소드의 매개변수인 콜백 함수는 다음 네 가지 매개변수와 함께 호출된다.
    • accumulator : 누적값
    • currentValue : 현재 요소
    • currentIndex : 현재 요소의 인덱스
    • array : 순회 중인 배열

initialValue

  • 최초 호출에서 첫 번째 인수(acc)에 제공하는 값
  • 초기값을 제공하지 않으면 배열의 첫 번째 요소를 사용한다.

_.reduce()

_.reduce(arr, iteratee, initVal) : 배열을 순회하며 각 요소에 iteratee 함수를 적용하여 그 결과값을 계속해서 누적하고, 최종적으로 누적된 결과값을 리턴하는 함수

_.reduce = function (arr, iteratee, initVal) {
  let acc = initVal; // acc(누적값) = initVal(초기값)
  // 이때 initVal은 값 또는 undefined

  _.each(arr, function(el, idx, collection) {
    // 초기값이 없는 경우, 배열의 arr[0]을 초기값으로 설정해준다.
    if(acc === undefined) {
      acc = arr[0];
    // 초기값이 있는 경우, 배열을 순회하며 iteratee 함수를 실행하고, 결과값을 acc에 재할당한다.
    } else {
      acc = iteratee(acc, el, idx, collection);
    }
  });

  return acc;
};
profile
개발 공부 기록 블로그

0개의 댓글