언더스코어 함수구현 part 1.
멘탈 터져서 하루종일 멍하니 페어가 시키는대로 손가락만 움직이다 일정이 끝나버렸다.
오늘 이거 박살내고 잔다. 다 죽어따 진짜...
_.each = function (collection, iteratee) {
if(Array.isArray(collection)) {
for(let i = 0; i < collection.length; i++) { // collection이 배열이면
iteratee(collection[i], i, collection);
}
} else if(typeof collection === 'object') { // collection 이 객체면
for (let key in collection) {
iteratee(collection[key], key, collection);
}
}
};
each 함수에는 collection(배열이나 객체) 와 정체모를 함수 iteratee 가 인자로 들어온다.
물론 인자의 명칭은 아무 상관도 없으므로 (tomato, potato) 가 되어도 무관하다. iteratee 함수에 요소(값), 인덱스(키), 배열(객체) 순으로 인자를 처리해 collection에 적용하는게 핵심이다.
배열은 요소들에 순서가 있으나 객체는 순서가 없어 요소에 접근하는 방식에 차이가 생기므로, if문을 통해 collection 이 배열인지 객체인지를 구분해 따로 로직을 짜야 한다.
배열의 길이만큼 for문을 돌리면서 iteratee 함수를 실행시키는데, 그 인자로 배열의 요소, 인덱스, 배열 을 순서대로 집어넣고 실행시킨다. iteratee 함수는 그 3가지 인자를 다뤄서 정체모를 계산작업을 하게 된다.
collection이 객체인 경우에도 접근하는 문법의 종류만 다르고 작동원리는 위에 배열이랑 똑같다. collection 의 key 에 for문으로 접근해서 iteratee 함수에 value, key, 객체 인자를 넣어 실행시킨다.
_.filter = function (arr, test) {
let newArr = [];
_.each(arr, function(ele) {
if(test(ele)) {
newArr.push(ele)
}
});
return newArr;
};
arr 요소들에 test함수를 적용해 결과가 true 인 요소만을 담은 새로운 배열을 리턴시키는 filter 함수. test 는 인자를 입력받아 boolean 값을 리턴하는 함수다.
each 함수를 통해 filter의 핵심기능을 구현할건데, 1번인자로 filter함수의 인자인 arr를 입력한다. 그리고 2번인자로 익명함수를 하나 구현해서 입력한다. 이 익명함수가 하는 일은 1번 인자의 요소들에 test 함수를 적용해 true인 것만 newArr 에 푸쉬하는 것이다.
_.reject = function (arr, test) {
let newArr = [];
return _.filter(arr, function(item) {
return !test(item)
})
_.uniq = function (arr) {
let newArr =[];
_.each(arr, function(el) {
for(let i = 0; i < newArr.length; i++) {
if(el === newArr[i]) {
return;
}
}
newArr.push(el)
});
return newArr;
};
arr에서 중복되는 요소를 뺀 요소를 담은 새로운 배열을 리턴하는 함수. each함수를 필수적으로 사용해야 하는 조건이 걸려있다.
each함수를 실행시키면서 두 번째 인자로 익명함수를 넘겨준다. 이 익명함수는 for문을 돌리며 newArr 내부에 el과 동일한 요소가 존재하지 않으면 그 el을 newArr에 푸쉬한다.
가령 arr의 2번 인덱스 요소인 '2'가 이미 newArr에 푸쉬되었다면, 3번 인덱스에 중복되는 '2'가 존재하더라도 if문에서 걸리기 때문에 푸쉬되지 않고 리턴되어 버린다.
each함수 때문에 return되었다고 함수가 끝나는게 아니라 그 다음 el 이 투입되어 다음 for문이 또 돌아간다.
_.map = function (arr, iteratee) {
let newArr = [];
_.each(arr, function(ele) {
newArr.push(iteratee(ele));
});
return newArr;
}
arr의 각 요소에 iteratee 함수를 적용해서 그 결과요소들을 담은 새로운 배열을 리턴하는 함수.
슬슬 익숙해진다. each 함수를 사용해야 하고, 상술한 기능을 수행하는 익명함수를 2번째 인자에 넣어야 할거다.
map함수와 each함수의 차이는 each 함수는 그저 2번째인자로 들어오는 함수를 arr의 각 요소에 적용하는 데 그치지만, map 함수는 적용된 요소들을 새로운 배열에 담아서 그 배열을 리턴하는 기능까지 한다는 점이다.
이 점을 체크하고 아래 함수를 보자.
_.pluck = function (arr, keyOrIdx) {
return _.map(arr, function(ele) {
return ele[keyOrIdx];
})
}
map 함수를 사용해, 배열 또는 객체를 요소로 같는 배열 arr 에서 keyOrIdx 에 해당하는 요소를 찾아 그 요소를 담은 새로운 배열을 리턴하는 함수이다.
map 함수를 실행시키면서 두 번째 인자로 익명함수를 전달하는데, 이 익명함수는 arr의 keyOrIdx 요소를 찾아 리턴한다.
하나 더, each 함수는 내부에서 리턴되는 결과물이 없지만 map 함수는 직접 새로운 배열을 리턴하기 때문에 map() 을 리턴시키면 된다. 실수하기 쉬우므로 유의해야 한다.
응용하자면, 굳이 map함수를 쓰지 않고 each 함수만을 사용해 동일한 기능을 구현할 수도 있다. 이 경우 ele[keyOrIdx] 를 newArr 에 푸쉬하고 함수 종료 후 newArr을 리턴해주면 된다.
_.reduce = function (arr, iteratee, initVal) {
let acc = initVal;
_.each(arr, function(el, idx){
if(acc === undefined){
acc = el;
}else{
acc = iteratee(acc, el, idx, arr);
}
});
return acc;
};
arr 요소들에 iteratee 함수를 적용하면서 그 누적치를 쌓아올려 그 값을 리턴시키는 함수. iteratee 함수는 (누적치, 요소, 인덱스, 배열) 인자를 입력받을 수 있어야 한다. 최초에 누적치는 초기값, 즉 initVal 로부터 시작되므로 acc는 처음에 initVal 을 할당받는다.
each함수를 실행시키면서 2번째 인자로 익명함수를 전달한다. 중요한건 이 익명함수의 기능을 이해하는 것이겠지? if문은 initVal 이 설정되어 있는 경우와 아닌 경우의 분기를 나눈다. initVal에 값이 할당되지 않았다면 2번줄에서 acc 에 undefined 가 할당되었을 것이다.
acc가 undefined 인 경우, 가장 먼저 투입된 el, 즉 arr[0]을 acc에 재할당해줘야 한다. 그 다음 순회, arr[1]이 투입되었다. acc가 더이상 undefined가 아니니까 else문으로 빠진다.
이제 본격적으로 iteratee 함수가 작용하고, 그 결과값이 acc에 누적되기 시작한다.
iteratee 함수의 인자로 변수 acc, each함수를 통해 투입되는 arr의 요소 el과 인덱스 idx, arr 자체를 전달한다. 그러한 인자를 투입받아 interatee 는 어떤 기능을 수행하고, 그 결과값이 acc 에 저장된다. 저장된 acc 는 그 다음 순회에서 intertee 의 첫 번째 인자로 다시 전달된다. 이렇게 acc가 누적된다.