해당글은 오늘 공부한 내용을 정리하여 메모하는 형식으로 작성한 것으로
설명이나 이해를 돕는 글이 아님을 명시합니다.
배열이나 객체를 다루기 위한 간단한 Underbar 라이브러리 기능을 구현한다.
underscore.js, lodash등과 같은 라이브러리를 모티브로 JS의 내장메소드의 기능을 구현해본다.
let _ = {}
_.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;
};
start
가 undefined인 경우, slice는 0부터 동작end
가 생략될 경우(undefined), slice는 마지막 인덱스까지 동작한다.end
가 배열의 범위를 벗어날 경우, slice는 마지막 인덱스까지 동작한다.start
가 배열의 범위를 벗어날 경우, 빈 배열을 리턴한다._.take = function (arr, n) {
if (n === undefined || n < 0) {
return [];
}
if (n > arr.length) {
return [...arr];
}
let result = _.slice(arr, 0, n);
return result;
};
Spread Syntax 사용
let copy = arr
를 사용하려고 했는데 코드의 가독성을 위해서 spread syntax 사용했다.let copy = arr
와 [...array]
의 복사 방식이 다르지 않을까 생각하여 찾아보다가 배열의 깊은 복사와 얕은 복사에 대해서 정리가 필요하다고 느껴졌다._.drop = function (arr, n) {
if (n > arr.length) {
return [];
}
if (n === undefined || n < 0) {
return [...arr];
}
let result = _.slice(arr, n);
return result;
};
_.last = function (arr, n) {
let length = arr.length;
if (n === undefined || n < 0) {
return [arr[length - 1]];
}
if (n > arr.length) {
return [...arr];
}
let result = _.drop(arr, length - n);
return result;
};
_.each = function (collection, iteratee) {
if (Array.isArray(collection)) {
for (let i = 0; i < collection.length; i++) {
iteratee(collection[i], i, collection);
}
} else {
for (let item in collection) {
iteratee(collection[item], item, collection);
}
}
};
_.indexOf = function (arr, target) {
// 배열의 모든 요소에 접근하려면, 순회 알고리즘(iteration algorithm)을 구현해야 한다.
// 반복문을 사용하는 것이 가장 일반적이지만, 지금부터는 이미 구현한 _.each 함수를 활용하여야 한다.
let result = -1;
_.each(arr, function (item, index) {
if (item === target && result === -1) {
result = index;
}
});
return result;
};
_.filter = function (arr, test) {
// TODO: 여기에 코드를 작성한다.
// arr를 순회하면서 test 함수가 true인 요소 찾기
// 그 요소들의 새로운 배열 리턴
let result = [];
_.each(arr, function (item) {
if (test(item)) {
result.push(item);
}
});
return result;
};
_.reject = function (arr, test) {
// TODO: 여기에 코드를 작성한다.
// TIP: 위에서 구현한 `filter` 함수를 사용해서 `reject` 함수를 구현해 보세요.
let result = [];
_.each(arr, function (item) {
if (!test(item)) {
result.push(item);
}
});
return result;
};
_.uniq = function (arr) {
// TODO: 여기에 코드를 작성한다.
// arr를 순회하면서 item이 다른 item과 같은게 있는지 확인
// 같은게 있다면 그냥 넘어감
// 없으면 result에 추가
let result = [];
for (let i = 0; i < arr.length; i++) {
let diffIndex = _.indexOf(arr, arr[i]);
if (diffIndex === i) {
result.push(arr[i]);
}
}
return result;
};
_.map = function (arr, iteratee) {
// _.map 함수는 매우 자주 사용됩니다.
// _.each 함수와 비슷하게 동작하지만, 각 요소에 iteratee를 적용한 결과를 리턴한다.
// arr를 순회하면서 각 요소를 iteratee에 적용한다.
// 적용된 요소를 새배열로 리턴
let result = [];
_.each(arr, function (item, index) {
result.push(iteratee(item, index, arr));
});
return result;
};
_.pluck = function (arr, keyOrIdx) {
// _.pluck을 _.each를 사용해 구현하면 아래와 같습니다.
// let result = [];
// _.each(arr, function (item) {
// result.push(item[keyOrIdx]);
// });
// return result;
// _.pluck은 _.map을 사용해 구현
// arr를 순회하면서 keyOrIdx를 가지는 요소값 추출
// 없으면 result에 undefined를 push
// 있으면 result에 push
let result = _.map(arr, function (item, index) {
if (item[keyOrIdx]) {
return item[keyOrIdx];
} else {
return undefined;
}
});
return result;
};
_.reduce = function (arr, iteratee, initVal) {
// 배열을 순회하며 각 요소에 iteratee 함수를 적용하고,
// iteratee 초기값 여부
// iteratee 초기값 O => initVal 적용 및 arr 그대로
// iteratee 초기값 X => initVal = arr[0] 이고 arr[1] - arr[n] 반복
// 그 결과값을 계속해서 누적(accumulate)한다.
// 최종적으로 누적된 결과값을 리턴한다.
let acc;
if (initVal === undefined) {
acc = arr[0];
let newArray = _.slice(arr, 1);
_.each(newArray, function (item, index) {
acc = iteratee(acc, item, index, arr);
});
} else {
acc = initVal;
_.each(arr, function (item, index) {
acc = iteratee(acc, item, index, arr);
});
}
return acc;
};
초기값 설정과 누적값 처리에 대해 고민
_.once = function (func) {
// 이 함수가 처음 호출됐는지를 확인
// 처음이면 콜백 함수를 정상적으로 호출하고
// 만약 처음 호출이 아니면 이전 값을 리턴한다.
let called = false;
let result;
return function () {
// TIP: arguments 키워드 혹은, spread operator를 사용하세요.
if (!called) {
result = func(...arguments);
}
called = true;
return result;
};
};
클로저
여기서 고차함수, 클로저함수, 콜백함수, 커링 함수의 차이점에 대해서 페어와 이야기를 나누게 되었고 이에 대한 내용은 따로 포스팅을 할 예정이다.
_.delay = function (func, wait, ...value) {
setTimeout(() => func(...value), wait);
};
자바스크립트 내장 배열 메소드를 직접 구현
_.includes = function (arr, target) {
// 반복문을 돌면서 각 요소들을 target과 비교한다.
// 일치하면 true 리턴하고
// 반복문 끝까지 일치하는게 없으면 false를 리턴한다.
let result = false;
_.each(arr, (element) => {
if (element === target) {
result = true;
}
});
return result;
};
primitive value
_.every = function (arr, iteratee) {
//빈 배열을 입력받은 경우
// true를 리턴
if (!arr.length) {
return true;
}
//iteratee가 주어졌는지 확인
if (iteratee === undefined) {
//iteratee가 없으면 각 요소가 truthy인지 확인
for (const element of arr) {
if (!element) {
return false;
}
}
} else {
//반복문 돌면서 각 요소에 iteratee 를 적용해서
// 모든 요소가 true면 true를 return
// 하나라도 false면 false를 return
for (const element of arr) {
if (!iteratee(element)) {
return false;
}
}
}
return true;
};
_.some = function (arr, iteratee) {
// TODO: 여기에 코드를 작성한다.
// 빈 배열인 경우에 false를 리턴
if (!arr.length) {
return false;
}
//iteratee가 주어졌는지 확인
if (iteratee === undefined) {
//iteratee가 없으면 배열의 요소 중 하나라도 truthy이면 true를 return
for (const element of arr) {
if (element) {
return true;
}
}
} else {
//반복문을 돌면서 배열의 요소 중 하나라도 true면 true를 return
//그 외는 false를 return
for (const element of arr) {
if (iteratee(element)) {
return true;
}
}
}
return false;
};
자바스크립트 객체를 더 쉽게 다룰 수 있는 커스텀 객체 메소드를 구현
_.extend = function (obj1, ...objs) {
// objs = [obj1, obj2, obj3, ...]
// 반복문을 돌면서(_.each) 첫번째 obj에 나머지 obj를 순서대로 추가해준다.
// 만약 첫번째 obj에 동일한 key가 존재하면
// 새로운 value로 덮어씌운다
// 바뀐 첫번째 obj를 return 한다.
/*
if (_.every(objs, (obj) => Object.keys(obj).length === 0)) {
return objs[0];
}*/
_.each(objs, (obj) => {
for (let key in obj) {
//반복문을 써서 obj1에 덮어씌우기
//obj1의 reference는 유지된다.
obj1[key] = obj[key];
}
});
return obj1;
};
let obj1 = { key1: 'something' };
_.extend(
obj1,
{
key2: 'something new',
key3: 'something else new',
},
{
blah: 'even more stuff',
key3: 'overwrite"
}
);
console.log(Object.keys(obj1)) // --> key1, key2, key3, blah
console.log(obj1.key3) // --> 'overwrite"
_.defaults = function (obj1, ...objs) {
// TODO: 여기에 코드를 작성한다.
_.each(objs, (obj) => {
for (let key in obj) {
//반복문을 써서 obj1에 덮어씌우기
//obj1에 key가 존재하면 아무것도 안한다.
//key가 존재하지않으면 추가한다.
if (!obj1.hasOwnProperty(key)) {
obj1[key] = obj[key];
}
}
});
return obj1;
};
_.zip = function (...arrs) {
// TODO: 여기에 코드를 작성한다.
// 여러개의 배열에서 동일한 index의 값으로 새로운 배열을 만든다.
// arrs 반복문을 돌면서 각각의 요소값을 배열로 만들어준다.
// 그 배열을 result라는 새 배열에 push 해준다.
let result = [];
let longest = 0;
for (const arr of arrs) {
if (arr.length > longest) {
longest = arr.length;
}
}
for (let i = 0; i < longest; i++) {
result.push(_.pluck(arrs, i));
}
return result;
};
};
_.zipStrict = function (...arrs) {
// TODO: 여기에 코드를 작성한다.
let result = [];
let shortest = 99999;
for (const arr of arrs) {
if (arr.length < shortest) {
shortest = arr.length;
}
}
for (let i = 0; i < shortest; i++) {
result.push(_.pluck(arrs, i));
}
return result;
};
_.intersection = function (set1, ...sets) {
// TODO: 여기에 코드를 작성한다.
// 반복문을 돌면서 set1의 요소가 set2에 있는지 확인
// set2에 있으면 새로운 배열에 추가
// 없으면 아무것도 안한다.
let result = [];
_.each(set1, (item) => {
//set1의 각 요소가 sets에 있는 모든 배열에 있는지 확인
if (
/* every 함수로 sets의 모든 요소에 item이 있는지 확인 */
_.every(sets, (set) => {
//indexOf 함수를 사용해서 item이 존재하는지 확인
// item이 없으면 -1을 return 하기 때문에 0 보다 큰지 확인
if (_.indexOf(set, item) >= 0) {
return true;
}
return false;
})
) {
result.push(item);
}
});
return result;
};
const set1 = ['a', 'e', b', 'c'];
const set2 = ['c', 'd', 'e'];
const set3 = ['a', 'b', 'e'];
const result = _.intersection(set1, set2);
console.log(result) // --> ['e', 'c'] => 첫 번째 배열에 'e'가 먼저 등장
_.difference = function (set1, ...sets) {
// TODO: 여기에 코드를 작성한다.
let result = [];
_.each(set1, (item) => {
if (
!_.some(sets, (set) => {
if (_.indexOf(set, item) < 0) {
return false;
}
return true;
})
) {
result.push(item);
}
});
return result;
};
const set1 = ['a', 'b', 'c']; [1, 2, 3, 4],
const set2 = ['b', 'c', 'd']; [2, 30, 40],
const set3 = ['a'] [1, 11, 111]
const result = _.difference(set1, set2);
console.log(result) // --> [3, 4]
Repeat again
해당문제는 _.some 사용에 있어서 아직 납득되지 않는 부분이 있어서 다시 테스트 케이스를 넣어서 적용해 볼 필요가 있다.
_.sortBy = function (arr, transform, order) {
// transform이 있으면 transform을 사용해서 sort를 한다.
// 없으면 배열의 요소 값 자체에 대한 비교 연산자의 결과를 따른다.
// order = undefined 이거나 1 이면 오름차순, -1 이면 내림차순
// transform이 있는지 확인
let result = [...arr];
if (transform !== undefined) {
if (order === undefined || order === 1) {
result.sort((a, b) => {
if (transform(a) > transform(b)) {
return 1;
}
if (transform(a) < transform(b)) {
return -1;
}
return 0;
});
} else {
result.sort((a, b) => {
if (transform(a) > transform(b)) {
return -1;
}
if (transform(a) < transform(b)) {
return 1;
}
return 0;
});
}
} else {
if (order === undefined || order === 1) {
result.sort((a, b) => a - b);
} else {
result.sort((a, b) => b - a);
}
}
return result;
};
아직 spread syntax를 사용하는게 익숙하지 않아서 구현하면서 중간에 헷깔리는 경우도 있었다.
그리고 함수를 매개변수로 갖는 함수(고차함수)를 가져와서 기능을 사용하려고 할때 어떤 인자값을 넣어야하는지 로직의 흐름이 어떻게 흘러가는 지 그려지지 않아서 처음에는 조금 고생했지만 여러 함수를 구현해보면서 조금은익숙해지고 흐름이 그려지는 같다.