Underbar 과제

유슬기·2023년 1월 24일
0

프론트엔드

목록 보기
27/64
post-thumbnail

Underbar 과제: underscore.js나 lodash 같은 라이브러리를 만들어보기

bareMinimum

_.each

_.each = function (collection, iteratee) {
  /*  
  1. collection(배열 혹은 객체)과 함수 iteratee(반복되는 작업)를 인자로 전달받아 
  	 (iteratee는 함수의 인자로 전달되는 함수이므로 callback 함수)
  2. collection의 데이터(element 또는 property)를 순회하면서
  3. iteratee에 각 데이터를 인자로 전달하여 실행합니다.
  */
  
  // 배열을 받으면 실행
  if (Array.isArray(collection)) {
    for (let i = 0; i < collection.length; i++) {
      iteratee(collection[i], i, collection)
    }
  }
  // 배열이 아니면(객체이면) 실행 
  else {
    for (const key in collection) {
      iteratee(collection[key], key, collection)
    }
  }
};

인자로 전달받은 배열 또는 객체(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;
};

인자로 전달받은 배열에 target(요소)이 포함되어 있다면, 해당 인덱스를 리턴한다.
배열 내 target이 중복해서 존재하는 경우, 가장 낮은 인덱스를 리턴한다.
배열에 해당 요소가 없다면 -1을 리턴한다.

_.uniq

// _.uniq는 주어진 배열의 요소가 중복되지 않도록 새로운 배열을 리턴합니다.
// 중복 여부의 판단은 엄격한 동치 연산(strict equality, ===)을 사용해야 합니다.
// 입력으로 전달되는 배열의 요소는 모두 primitive value라고 가정합니다.
_.uniq = function (arr) {
  let result = [];
  _.each(arr, (el) => {
    // 위에 있는 _.indexOf 함수를 활용, 리턴 값이 -1이면 배열에 요소가 포함되지 않았다는 의미
    if (_.indexOf(result, el) === -1) { 
      result.push(el) // result에 arr의 el이 없으면 푸시
    }
  })
  return result;
};

인자로 전달받은 배열의 요소가 중복되지 않도록 새로운 배열을 리턴한다.

_.filter

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

인자로 전달받은 배열의 모든 요소를 test 함수에 전달하고, test(요소)의 결과 값이 truthy일 경우 해당 요소를 새로운 배열에 담아 리턴한다.

_.map

// _.map은 iteratee(반복되는 작업)를 배열의 각 요소에 적용(apply)한 결과를 담은 새로운 배열을 리턴합니다.
// 함수의 이름에서 드러나듯이 _.map은 배열의 각 요소를 다른 것(iteratee의 결과)으로 매핑(mapping)합니다.
_.map = function (arr, iteratee) {
  let result = [];
  _.each(arr, (el) => {
    result.push(iteratee(el)) // iteratee 함수의 리턴 값을 result 배열의 요소로 push
  })
  return result;
};

전달받은 배열의 모든 요소를 각각 iteratee 함수에 전달하여 해당 함수의 결과값을 담은 새로운 배열을 리턴한다.

_.reduce

//  1. 배열을 순회하며 각 요소에 iteratee 함수를 적용하고,
//  2. 그 결과값을 계속해서 누적(accumulate)합니다.
//  3. 최종적으로 누적된 결과값을 리턴합니다.
_.reduce = function (arr, iteratee, initVal) {
  let acc = initVal; // 누적값을 할당해줄 acc 변수 선언 및 initVal(초기값)을 할당(첫번째 요소부터 else문 누산 시작)
  _.each(arr, (el, idx) => {
    // initVal이 없으면 첫번째 요소를 acc에 할당(두번째 요소부터 else문 누산 시작)
    if (acc === undefined && idx === 0) {
      acc = el;
    }
    // initVal이 있거나, 위의 if 문에서 acc에 값을 할당해준 경우 iteratee 함수 리턴 값을 acc에 할당
    else {
      acc = iteratee(acc, el, idx, arr)
    }
  })
  return acc;
};

전달받은 배열로부터 iteratee의 내용이 무엇인지에 따라서 하나의 결과값을 반환한다.
initVal는 초기값으로, 이 initVal가 주어지느냐, 주어지지않느냐의 두가지 경우로 나뉜다.


advanced

_.once

// _.once는 callback 함수를 한 번만 호출하는 '함수'를 리턴합니다.
// _.once가 리턴하는 함수를 여러 번 호출해도 callback 함수는 한 번 이상 호출되지 않습니다.
_.once = function (func) {
  // 호출 여부를 확인할 변수 선언 및 초기값 false 할당
  let isCalled = false;
  let result;
  return function () {
    if (isCalled === false) {
      result = func(...arguments);
      isCalled = true;
    }
    return result;
  };
};

_.delay

// _.delay는 입력으로 전달되는 시간(ms, 밀리초)후 callback 함수를 함께 전달되는 (임의의 개수의) 인자와 함께 실행합니다.
// 예를 들어, _.delay(func, 500, 'a', 'b')의 결과로 '최소' 500m가 지난 이후에 func('a', 'b')가 호출됩니다.
_.delay = function (func, wait, ...arg) {
  setTimeout(func, wait, ...arg);
};

_.includes

// _.includes는 배열이 주어진 값을 포함하는지 확인합니다.
// 일치 여부의 판단은 엄격한 동치 연산(strict equality, ===)을 사용해야 합니다.
// 입력으로 전달되는 배열의 요소는 모두 primitive value라고 가정합니다.
_.includes = function (arr, target) {
  let result = false;
  _.each(arr, function (el) {
    if (el === target && result === false) {
      result = true;
    }
  });
  return result;
};

전달받은 배열에 target이 포함되어 있는 지 확인하여 포함 여부를 boolean 타입으로 리턴한다.

_.every

// _.every는 배열의 모든 요소가 test 함수(iteratee)를 통과하면 true를, 그렇지 않은 경우 false를 리턴합니다.
// test(element)의 결과(return 값)가 truthy일 경우, 통과입니다.
// _.each를 반드시 사용할 필요는 없습니다.
// iteratee가 주어지지 않을 경우, 모든 요소가 truthy인지 확인합니다.
// 빈 배열을 입력받은 경우, true를 리턴해야 합니다. (공허하게 참, vacantly true)
_.every = function (arr, iteratee) {
  // 빈 배열을 입력받은 경우, true 리턴
  if (arr.length === 0) {
    return true;
  }  

  let result = false;
  // 배열의 요소 전체를 순환
  for (let el of arr) {
    // iteratee가 주어지지 않을 경우, 모든 요소가 truthy인지 확인
    if (iteratee === undefined) {
      if (el === true) {
        result = true;
      } else {
        return false; // 요소 중 하나라도 falsy라면 바로 false 리턴해주기 (반복문 종료) 
      }
    }
    // iteratee가 주어지면 iteratee 함수에 요소가 전달인자로 들어감, iteratee의 결과값이 truthy인지 확인
    else if (iteratee(el)) {
      result = true;
    }
    // 인자가 거짓일때 false
    else {
      return false; // 결과값 중 하나라도 falsy라면 바로 false 리턴해주기 (반복문 종료)
    }
  }
  return result;
};

_.some

// _.some은 배열의 요소 중 하나라도 test 함수(iteratee)를 통과하면 true를, 그렇지 않은 경우 false를 리턴합니다.
// 빈 배열을 입력받은 경우, false를 리턴해야 합니다.
// 그 외 조건은 앞서 _.every와 동일합니다.
_.some = function (arr, iteratee) {
  // 빈 배열을 입력받은 경우, false 리턴
  if (arr.length === 0) {
    return false;
  }  

  let result = true;
  // 배열의 요소 전체를 순환
  for (let el of arr) {
    // iteratee가 주어지지 않을 경우, 요소 중 하나라도 truthy인지 확인
    if (iteratee === undefined) {
      if (el === true) {
        return true; // 요소 중 하나라도 truthy라면 바로 true 리턴해주기 (반복문 종료)
      } else {
        result = false;
      }
    }
    // iteratee가 주어지면 iteratee 함수에 요소가 전달인자로 들어감, iteratee의 결과값이 하나라도 truthy인지 확인
    else if (iteratee(el)) {
      return true; // 결과값 중 하나라도 truthy라면 바로 true 리턴해주기 (반복문 종료)
    }
    // 인자가 거짓일때 false
    else {
      result = false;
    }
  }
  return result;
};

_.extend

// _.extend는 여러 개의 객체를 입력받아, 순서대로 객체를 결합합니다.
// 첫 번째 입력인 객체를 기준으로 다음 순서의 객체들의 속성을 덮어씁니다.
// 최종적으로 (속성이 추가된) 첫 번째 객체를 리턴합니다. (새로운 객체 X)
// _.extend로 전달되는 객체의 수는 정해져 있지 않습니다.
// _.each를 사용해서 구현합니다.
_.extend = function (obj1, ...objs) {
  // objs = [{},{},{},{},...], el = {}(objs 내의 각 객체)
  _.each(objs, (el) => {
    Object.assign(obj1,el)
  })
  return obj1
};

_.defaults

// _.defaults는 _.extend와 비슷하게 동작하지만, 이미 존재하는 속성(key)을 덮어쓰지 않습니다.
_.defaults = function (obj1, ...objs) {
  // objs = [{},{},{},{},...], el = {}(objs 내의 각 객체), key = 각 객체의 속성
  _.each(objs, (el) => {
    for (let key in el) {
      if (!(key in obj1)) { // 전달받은 인자 중 1번째 객체에 key가 없을 때,
        obj1[key] = el[key] // 1번째 객체 key 속성에 el의 key 속성 값을 넣어주기
      }
    }
  })
  return obj1;
};
profile
아무것도 모르는 코린이

0개의 댓글