underbar 란?
과거 웹 브라우저에서 사용되는 Javascript에서는 배열 메소드를 지원하지 않았다. 따라서 배열과 객체를 다루기 위한모음집이 개발되기 이르렀는데, 이게 바로 underbar.js 다. Underbar의 모티브는 underscore.js와 lodash가 있다.
지금이야 웹 브라우저에서 작동하는 Javascript에서 배열과 객체를 다루기 위한 고차함수를 지원하지만, underbar를 직접 구현하는 과정은 Javascript의 동작원리를 이해하는데 큰 도움이 된다.
Collection 이란?
Collection이란 데이터의 모음을 의미한다. 여러개의 데이터를 한 군데에 모아놓은 자료구조를 의미하며, 대표적으로 배열과 객체가 있다. 배열은 데이터를 순서대로 모아놓은 자료구조이며, 객체는 서로 관련있는 데이터들을 key~value 형태로 순서대로 모아놓은 자료구조다.
slice(array, start, end)
slice() 메소드는 start 인덱스부터 end 인덱스 이전 요소까지 shallow copy 한 새로운 배열을 리턴한다. 원본 배열은 변경되지 않는다.
shallow copy란?
Javascript에서는 얕은복사(shallow copy)와 깊은 복사(Deep Copy)가 있다. 얕은 복사(Shallow Copy)는 참조값의 복사를 의미한다. 즉 데이터의 주소값만 복사해오는 것이다. 얕은 복사를 진행할 경우 원본 데이터가 변경되어 버린다.
깊은 복사(Deep Copy)는 데이터 값 자체를 복사한다. 새로운 데이터를 변경하여도 기존 값이 변경되지 않는다. 주소값이 아닌 값 자체를 복사했기 때문이다.
_.slice = function (arr, start, end) {
let _start = start || 0,
_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;
};
take()
take() 메소드는 배열의 N개의 Element를 담은 새로운 배열을 Shallow Copy한 배열을 반환하다. N이 음수이거나 전역 객체인 undefined인 경우 빈 배열을 반환한다. 반약 N이 배열의 길이을 벗어나면 전체 배열을 얕은 복사를 한 샐로운 배열을 반환한다.
_.take = function (arr, n) {
let result = [];
if(n < 0 || n === undefined){
return result;
}
if(n > arr.length){
result = arr;
return result;
}
for(let i=0; i<n; i++){
result.push(arr[i]);
}
return result;
};
drop()
drop() 메소드는 N개의 Element를 제외한 새로운 배열을 반환한다. N이 음수이거나 undefined인 경우 전체 배열을 얕은 복사한 새로운 배열을 반환한다. N이 배열의 길이보다 큰 경우 빈 배열을 반환함
_.drop = function (arr, n) {
let result = [];
if(n<0 || n === undefined){
result = arr;
return result;
}
if(n > arr.length){
return result;
}
for(let i=n; i<arr.length; i++){
result.push(arr[i]);
}
return result;
};
last()
last() 메소드는 배열의 마지막 N개의 Element를 담은 새로운 배열을 반환한다. N이 음수이거나 undefined인 경우 배열의 마지막 요소만 담아서 반환한다.
배열의 길이를 벗어난 N이 주어지는 경우 전체 배열을 얕은 복사한 새로운 배열을 반환한다.
_.last = function (arr, n) {
let result = [];
if(n < 0 || n === undefined){
result.push(arr[arr.length-1]);
return result;
}
if(n>arr.length){
result = arr;
return result;
}
for(let i=arr.length-n; i<arr.length; i++){
result.push(arr[i]);
}
return result;
};
each()
each() 메소드는 Collection 자료구조의 반복적인 작업을 수행한다. 인자로 Collection과 Callback 함수를 전달받아 Collection을 순회하면서 Callback의 결과값을 리턴한다.
대표적인 Collection인 배열과 객체를 모두 처리할 수 있어야 한다. 인자로 전달되는 매개변수는 데이터, 접근자(index || 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 i=0; i<Object.keys(collection).length; i++){
iteratee(Object.values(collection)[i], Object.keys(collection)[i], collection )
}
}
};
indexOf()
indexOf() 메소드는 배열 Collection에서 target 데이터의 위치(Index)를 반환한다. 만약 target 데이터가 Collection내 존재하지 않는 경우 -1을 반환한다.
IndexOf() 메소드는 Collection 데이터에서 가장 앞에 있는 target 데이터의 위치를 반환한다.
_.indexOf = function (arr, target) {
let result = -1;
_.each(arr, function (item, index) {
if (item === target && result === -1) {
result = index;
}
});
return result;
};
filter()
filter() 메소드는 test callback 함수를 통과하는 요소를 모은 새로운 배열을 반환한다.
_.filter = function (arr, test) {
let result = [];
_.each(arr, function(item){
if(test(item)) result.push(item);
})
return result;
};
reject()
reject() 메소드는 Test Callback 함수를 통과하지 못한 요소들을 모은 새로운 배열을 반환한다.
_.reject = function (arr, test) {
let result = [];
_.each(arr, (item)=>{
if(!test(item)) result.push(item);
})
return result;
};
uniq()
uniq() 메소드는 배열의 요소가 중복되지 않도록 새로운 배열을 반환한다. 엄격한 동치 연산(===)을 통해 진행해야 하며 입력으로 전달되는 배열의 요소는 참조변수가 아닌 원시값(문자열, 숫자 등)이다.
_.uniq = function (arr) {
let result = [];
_.each(arr, (item, idx)=>{
if(_.indexOf(arr, item)===idx) result.push(item);
});
return result;
};
map()
map() 메소드는 Callback() 메소드를 실행한 결과값을 모은 새로운 배열을 반환한다. 실제 가장 많이 사용되는 Collection 메소드다.
_.map = function (arr, iteratee) {
let result = [];
_.each(arr, (item)=>{
result.push(iteratee(item));
})
return result;
};
pluck()
pluck() 메소드는 객체 또는 배열을 요소로 소유한 배열을 입력받는다. 또한 Collection 데이터에서 찾고자 하는 key/Index 값을 입력받아 해당 요소들만 추출한 새로운 배열을 반환한다.
let result = [];
_.each(arr, (item)=>{
result.push(item[keyOrIdx])
});
return result;
reduce()
reduce() 메소드는 배열 Collection을 순회하면서 Callback 함수를 호출한 결과값을 누적하여 반환한다. 배열의 여러개의 정보가 REDUCTION된 최종값을 반환한다.
_.reduce = function (arr, iteratee, initVal) {
const [first, ...rest] = arr;
let result = initVal;
if(initVal == null){
result = first;
arr = rest;
}
_.each(arr, (item, idx)=>{
result = iteratee(result, item, idx, arr);
})
return result
};
once()
once() 메소드는 Callback() 메소드를 한번반 호출하는 함수다. 여러번 호출해도 더 이상 호출되지 않는다. 함수를 반환하는 클로져 함수를 구현해야 한다.
한번 함수가 호출되면 isCalled가 true로 변경된다. 이후 다시 호출을 하면 isCalled가 true이기 때문에 바로 리턴된다. 어떻게 이게 가능할까?
외부 함수 _.once()안에서 내부 함수 또는 클로져 함수 function() 에서는 외부 함수의 변수에 접근이 가능하다. 즉 _.once() 함수가 호출될 때 반환하는 함수에서 _.once() 함수의 내부 변수에 접근해서 함수 호출을 통제하는 것이다.
_.once = function (func) {
let isCalled, result;
// 클로저 함수
// isCalled에 접근이 가능함.
return function(){
if(isCalled) return result;
isCalled = true;
return result = func.apply(this, arguments);
}
};
delay()
delay() 메소드는 입력값으로 전달되는 값만큼 지연된 후 함수를 호출한다. delay() 메소드 또한 클로져 개념을 사용해서 구현할 수 있다. 함수를 리턴하게 되며 내부 함수는 외부 함수 _.delay()의 func, waitValue, args등의 내부 변수에 접근이 가능하다.
_.delay = function (func, wait) {
let [funcValue, waitValue, ...args] = arguments;
// 클로져 함수
return setTimeout(function(){
funcValue.apply(this, args);
}, waitValue)
};
includes()
include() 메소드는 인자로 전달된 Collection 배열내에 Target 값이 포함되어 있는지 확인 후 Boolean 값을 반환한다. 일치 여부는 엄격한 동치 연산(===)을 거쳐 필터링 한다. 입력값으로 전달되는 Target 값들은 모두 원시값이다.
_.includes = function (arr, target) {
let res = false;
_.each(arr, (item)=>{
if(item === target) res = true;
})
return res;
};
every()
every() 메소드는 배열의 모든 요소가 test Callback 메소드를 통과하는 경우 true를 반환하며, 한 요소라도 통과하지 못하면 false를 반환한다. 빈 배열을 전달받은 경우 true를 반환한다.
_.every = function (arr, iteratee) {
let res = true;
_.each(arr, (item)=>{
if(iteratee == null){
res = res && item;
return;
}
if(!iteratee(item)) res = res && false;
})
return res;
};
some()
some() 메소드는 전달받은 배열 요소 중 하나라도 Callback 메소드를 통과하면 true를 반환한다. 모든 경우가 false일 경우 false를 반환한다. 빈 배열을 입력받는 경우 false를 반환한다.
_.some = function (arr, iteratee) {
let res = false;
_.each(arr, (item)=>{
if(iteratee == null){
res = res || item;
return;
}
if(iteratee(item)){
res = res || true;
}
})
return res;
extend()
extend() 메소드는 여러개의 객체를 전달받는다. 순서대로 객체를 결합하며 첫번째 객체를 기준으로 다음 순서의 객체들의 값을 덮어쓰게 된다. 최종적으로 첫번째 객체를 반환하게 된다.
반복문 in과 of 차이점
반복문은 of와 in 두가지를 사용할 수 있다. 간단히 설명하자면 of는 배열에서 사용하고, in은 객체의 키값을 순회할 때 사용된다.
더 정확히 말하면 of는 value값을 순회하고, in은 키 값을 순회한다. 배열에서는 of(Element 값 순회)와 in(Index) 모두 사용가능하지만, 객체에서는 in(Key 순회)만 사용가능하다.
_.extend = function () {
let obj = arguments[0];
_.each(arguments, (item)=>{
// 키-값 쌍이 존재하는 경우 기존 값을 덮어쓴다.
// 키-값 쌍이 존재하지 않는 경우 새로 생성한다.
for(var key in item){
obj[key] = item[key];
}
return;
})
return obj;
defaults()
defaults() 메소드는 extend() 메소드와 동일한 작동 방식을 가진다. 첫번째 객체에 뒤이어 받은 객체값을 추가하지만 이미 존재하는 키~쌍 값에 새로운 값을 덮어쓰지 않는다.
_.defaults = function () {
let obj = arguments[0];
_.each(arguments, (item)=>{
for(var key in item){
if(obj[key] === undefined)
obj[key] = item[key];
}
return;
})
return obj;
};
zip()
zip() 메소드는 여러개의 배열을 입력받는다. 같은 index 요소들을 묶어 새로운 배열을 만든다. 최종적으로 반환하는 배열의 각 요소의 길이는 입력 배열의 가장 긴 요소로 결정한다. 만약 index가 존재하지 않는 경우 undefined를 반환한다.
_.zip = function () {
let max = arguments[0].length;
_.each(arguments, (item)=>{
if(max < item.length){
max = item.length;
}
return;
})
const result = [];
for(let i = 0; i<max; i++){
result.push(_.pluck(arguments, i));
}
return result;
};
zipStrict()
zipString() 메소드는 최종적으로 리턴되는 배열이 전달받은 배열의 요소 중 가장 짧은 요소의 길이를 따른다. 전체적인 기능은 zip() 메소드와 동일하게 작동한다.
.zipStrict = function () {
let min = arguments[0].length;
_.each(arguments, (item)=>{
if(min > item.length) min = item.length;
return;
})
const result = [];
for(let i = 0; i<min; i++){
result.push(_.pluck(arguments, i));
}
return result;
intersection()
intersection() 메소드는 여러개의 배열을 인자로 받아 교집합 배열을 반환한다. 모든 배열에 공통적으로 존재하는 요소만 추출하여 새로운 배열을 생성한다. 교집합 배열의 요소는 첫번째 배열을 기준으로 한다. 만약 교집합이 없는 경우 빈 배열을 반환한다.
_.intersection = function () {
let result = [];
let [first, ...ot ] = arguments;
for(var key of first){
if(_.every(ot, (item)=>{
for(var comp of item){
if(key === comp) return true;
}
})){
result.push(key);
}
}
return result;
};
difference()
difference() 메소드는 여러개의 배열을 입력받아 차집합 배열을 리턴한다. 배열의 첫번째 요소를 기준으로 하며, 차집합이 존재하지 않는 경우 빈 배열을 반환한다.
_.difference = function () {
let result = [];
let [first, ...ot ] = arguments;
for(let cur of first){
if(_.every(ot, (item)=>{
for(let temp of item){
if(temp === cur){
return false;
}
}
return true;
}))
result.push(cur);
}
return result;
};
sortby()
sortby() 메소드는 배열의 각 요소를 Callback 메소드를 사용해 변환한 결과를 토대로 비교 작업을 수행한다. number 타입간 비교는 대소비교를, string 타입 간 비교는 lexical 비교를 수행한다. 세번째 인자로 전달되는 order가 1이면 오름차순을, -1이면 내림차순을 수행한다.
.sortBy = function (arr, transform, order) {
let result = [];
for(let temp of arr){
result.push(temp);
}
if(transform === undefined){
result.sort();
}
else{
result.sort((a, b)=>{
let transA = transform(a);
let transB = transform(b);
if(transA > transB) return 1;
if(transB > transA) return -1;
return 0;
})
}
if(order === -1){
result.reverse();
}
return result;
};
shuffle()
shuffle() 메소드는 배열 요소 순서를 랜덤하게 배치하는 메소드다.
.shuffle = function (arr) {
// 배열 얕은 복사(shallow copy)
let result = arr.slice();
for(let i=0; i<result.length; i++){
const fromIdx = Math.floor(Math.random() * (0- result.length));
}
const temp = result[toIdx];
result[toIdx] = result[fromIdx];
result[fromIdx] = temp;
return result;
};
memoize()
memoize() 메소드는 메모이제이션(Memoization)을 적용한하여 이미 해결한 문제를 다시 풀지 않는 함수다. 위에서 살펴본 클로져 함수와 동일하게 외부 변수의 내부 변수를 내부 함수가 참조한다.
최초 함수가 호출되는 경우 어떤 특정 상태로 부터 시작하게 되고, 처음부터 함수를 호출할 필요가 없다. 메모이즈 함수를 사용하면 재귀 콜스택의 횟수를 획기적으로 줄일 수 있다.
_.memoize = function (func) {
var cache = {};
return function (){
if(cache[JSON.stringify(arguments)] === undefined){
cache[JSON.stringify(arguments)] = func.apply(this, arguments);
return cache[JSON.stringify(arguments)];
}else{
return cache[JSON.stringify(arguments)];
}
}
};
throttle()
throttle() 메소드는 ms로 전달되는 시간 동안 Callback 함수를 단 한번만 실행시키는 함수다.
_.throttle = function (func, wait) {
let flag = false;
return function(){
if(!flag){
func();
flag = true;
setTimeout(function(){
flag = false;
}, wait);
}
}
};
'Programming' 카테고리의 다른 글
[Javascript] 비동기 프로그래밍이란? Asynchronous Call (0) | 2022.05.18 |
---|---|
[Javascript] 명령형 vs 선언형 프로그래밍 (0) | 2022.05.17 |
[Javascript] Function arguments 속성이란? (0) | 2022.05.17 |
[Javascript] 비동기 프로그래밍이란? (0) | 2022.05.17 |
[Javascript] Array Function some, sort, every 사용법 (0) | 2022.05.15 |
댓글