코로 넘어져도 헤딩만 하면 그만
JS 고차함수 복습하기 본문
🚩고차함수란?
지난 세션에서 고차함수의 개념은 배웠으나, 사용이 익숙치 않다는 생각이 들어 복습을 해보았습니다. 우선 고차함수(higher order function)은 함수를 전달 인자로 받을 수 있고 함수를 리턴할 수 있는 함수를 말합니다. 자바 스크립트에서 함수는 특별 대우를 받는 '일급 객체'로 여겨지기 때문에, 다음과 같은 특징을 가진답니다.
1) 변수에 할당이 가능하다.
2) 다른 함수의 전달인자로 전달될 수 있다.
3) 다른 함수의 결과로 리턴될 수 있다.
즉, 함수는 변수에 할당이 가능하고, 이 변수를 다른 함수에서 전달인자로 받는 것도 가능합니다. 다시 말해 함수를 전달인자로 받는 거죠. 또 함수 내부에서 변수를 할당한 뒤 이 변수를 리턴하는 것도 가능해요. 아니면 변수를 쓰지 않고 함수 자체를 리턴할 수도 있죠.
(1)그렇다면 다른 함수의 인자로 전달되는 함수를 뭐라고 부를까요?
바로 '콜백 함수'(callback function)입니다. 함수를 인자로 전달받은 고차함수(caller)은 함수 내부에서 이 콜백 함수를 호출할 수 있습니다.
(2)그렇다면 함수를 리턴하는 함수는 뭐라고 부를까요?
이런 식의 함수를 고안한 하스켈 커리라는 사람의 이름을 따서 커링 함수라고 부릅니다.
고차함수는 커링 함수를 포함하는 개념으로 보면 될 것 같네요.
function callbackFunc(num) {
return num * 5;
}
function func1(func, num) {
return func(num);
}
let output = func1(callbackFunc, 4);
console.log(output); // -> 20
위와 같은 코드에서, 함수 callbackFunc은 func1의 인자로 들어오기 때문에 func1의 콜백함수라고 불립니다. 헷갈리지 않도록 단어를 잘 알아두는 게 중요할 것 같습니다.
🚩자바스크립트의 내장 고차 함수
자바 스크립트 내부에는 기본적으로 고차함수가 여럿 존재합니다. 그중 배열 메서드의 일부는 고차 함수에 해당되는데요. 자주 쓰이는 고차 함수 filter, map, reduce에 대해 알아보기로 했습니다.
1) filter
filter 함수는 모든 배열 요소 중에서 조건을 만족하는 요소만 걸러냅니다.
"조건 만족한 것만 걸러준다!"
filter은 배열의 각 요소가, 특정 함수에 따라 true일 경우, 따로 분류해냅니다.
여기서 말하는 특정 함수는 filter의 인자로 들어온 다른 함수입니다.
const isEven = function (num) {
return num % 2 === 0;
};
let arr = [1, 2, 3, 4];
let output = arr.filter(isEven);
console.log(output); // ->> [2, 4]
이처럼 filter 고차함수는 isEven이라는 조건을 담은 함수를 인자로 받아오고 있음을 볼 수 있습니다. 배열에서도 원하는 조건에 맞는 것들만 뽑아낼 때 filter을 쓰면 될 것 같습니다.
function keep(arr, keeper) {
return arr.filter(
function(el){
if(el === keeper){
return true;
} else {
return false;
}
})
}
let output = keep([1, 2, 3, 2, 1], 2)
console.log(output); // --> [2, 2]
배열 내부에 keeper로 들어온 값과 같은 요소가 존재할 때만 새 배열로 뽑기 위해 filter을 쓴 코드입니다. filter함수 내부에서 function으로 조건이 걸리는 것을 볼 수 있습니다. 내부 함수의 값이 true일 때만 조건 맞게 filtering되기 때문에 원하는 수가 같을 때만 새로운 배열 내부에 들어옵니다.
function filterOddLengthWords(words) {
//문자열을 요소로 갖는 배열을 입력받아
//그 길이가 홀수인 요소만을 갖는 배열을 리턴해야 합니다.
return words.filter(
function (x){
return x.length%2 ===1;
}
)
}
이 경우에도 filter함수에 다시 함수로 홀수의 조건을 걸어주었습니다.
따라서 문자열의 길이가 홀수인 요소만 새롭게 리턴됩니다.
(2)map
map 함수는 배열의 모든 요소에게 동일한 조건을 준 값을 배열로 반환합니다.
"존재하는 모든 요소에게 동일한 조건을 준다!"
배열의 각 요소가, 특정 함수에 의해, 다른 요소로 지정된다고 보면 되는데요. 이때 각 요소에게 조건을 주는 것이 인자로 들어오는 함수입니다. 이 함수에서 제시하는 '조건'에 따라서 결과도 달라지겠죠.
function getDoubledElements(arr) {
let newarr = arr.map(x=>{return x*2;}
// function (x){
// return x2;
// }
);
return newarr;
}
화살표 함수를 사용해서 적용해보았습니다. arr의 모든 요소들에게 2씩 곱해준 결과가 리턴되겠습니다.
이처럼 모든 요소를 순회하며 조건을 준다는 점에서 for문으로도 만들 수 있습니다. 하지만 모양이 복잡해질수록 고차함수의 유용성이 증가하기 때문에 map함수로도 만들 줄 알아야 합니다.
function checkEvenOrNot(arr) {
// 새로운 배열을 리턴해야 함. 변수로 새로운 배열 선언
// 각 요소가 2의 배수인지에 대한 정보를 if로 찾아냄 "ok" "no"
// arr.map(); 콜백 함수 안에 if문으로 구분하면 될 것 같다.
let newarr = arr.map(function(x){
//각 요소 x를 받아온다.
if(x%2 === 0 && x !== 0){
return "ok"
} else {
return "no"
}
})
return newarr;
}
배열 arr을 입력받아, 각 요소가 2의 배수인지 여부를 갖는 새로운 배열을 리턴합니다. map의 콜백 함수에 if를 사용하여 조건을 나누어주고 만들어진 새 배열을 리턴해주는 방식입니다.
아래와 같이 function sqare처럼 아예 외부에 함수를 만들어주고 바로 map에 적용해줄 수도 있습니다.
function square(number) {
return number * number;
}
function getSquaredElementsAtProperty(obj, property) {
// 키에 해당하는 obj의 key의 value가 배열인 경우,
// 배열의 각 요소를 제곱한 새 배열을 리턴한다.
// 변수 선언을 통해 새 배열
//키의 값이 배열인 경우. if문으로 실행.
//arr.map(); 안에 콜백함수로 square를 넣기
if (Array.isArray(obj[property])){
let arr = obj[property];
return arr.map(square);
} else {
return [];
}
}
만약 arr로 들어오는 정보를 사용하여, 18세 이상의 사람들의 이름을 걸러내고 싶을 때는 아래처럼 filter과 map을 같이 사용하면 됩니다. 우선 filter을 써서 age 조건이 18세 이상인 사람만 걸러 새 배열에 담은 뒤, 이 배열 각 요소에 map으로 이름을 걸러줍니다. 고차함수를 사용하면 짧고 편리하다는 사실이 느껴지나요?
function getOnlyAllowedToDrink(arr) {
//새 변수 선언 - arr.map()의 콜백함수로 각 요소인 x 받아오기
//x.age>=18세 이상 x.name 리턴
let newarr = arr.filter(function(x){
return x.age>=18
})
return newarr.map(function(x){
return x.name;
});
}
(3)reduce
reduce 함수는 여러 개의 데이터를 하나의 데이터로 응축시켜주는 함수입니다.
"여러 개의 데이터를 하나로 결합!"
배열의 각 요소를, 특정 함수에 따라, 원하는 '하나의' 형태로 응축하는데요. 정수 배열이면 전달받은 함수를 적용한 연산 결과로 합쳐주고, 문자열 배열이라면 문자열을 하나로 합쳐줍니다.
배열.reduce( function(acc, cur, index, arr) {
} , initialValue )
기본은 위와 같이 생긴 모습으로, 초기값initialValue을 제공하지 않으면 배열의 첫번째 요소를 사용합니다. acc는 기존까지의 누적값, cur은 현재값을 의미합니다. 주로 이 두 매개변수는 꼭 포함됩니다.
아래 코드는 reduce를 사용하여 들어오는 배열의 모든 요소 합을 구하는 식입니다.
function computeSumOfAllElements(arr) {
// 모든 요소의 합? arr.reduce()사용.
//초기값 설정해줄 수 있다.
let sum = arr.reduce(function(acc, cur){
//acc는 누적값, cur은 현재 요소
return acc+cur;
}, 0) //0은 초깃값을 설정해줬다.
return sum;
}
아래처럼 초깃값의 자리에 수가 아니라 빈 string, 혹은 빈 배열을 주어서 결과를 담을 수도 있습니다. 다음은 배열에서 가장 긴 문자열을 리턴하기 위한 코드입니다.
function getLongestElement(arr) {
return arr.reduce(function (acc, cur) {
if (acc.length >= cur.length) {
return acc;
} else {
return cur;
}
}, '');
}
이렇게 작성하면 배열에서 가장 긴 문자열의 길이도 리턴할 수 있겠죠.
function getLengthOfLongestElement(arr) {
if (arr.length === 0) {
return 0;
}
return arr.reduce(function (acc, cur) {
if (acc.length >= cur.length) {
return acc;
} else {
return cur;
}
}).length;
}
reduce를 사용하면, 다음과 같이 중첩된 2차원 배열을 받아 단일 배열에 담아줄 수도 있습니다.
function joinArrayOfArrays(arr) {
//[
// [1, 4],
// [true, false],
// ['x', 'y'],
// ]
let newarr = arr.reduce(function(acc,cur){ //
// 누적값 현재 요소 인덱스
// [] [1, 4] 0
//[1,4]
return [...acc,...cur];
},[]);
return newarr;
}
누적값에 둔 빈 배열에 ... spread 로 풀어준 값을 담는 것을 확인할 수 있습니다. 결과적으로
[1, 4, true, false, 'x', 'y']
라는 누적된 값만 리턴되게 됩니다.
이처럼 reduce는 단순히 수의 연산뿐만 아니라 배열이나 문자열도 다룰 수 있다는 점에서 유용하겠습니다.
🚩그렇다면 우리는 왜 고차함수를 사용해야 하는가?
이처럼 고차함수를 적극 사용하는 이유는 '추상화' 때문입니다.
복잡한 것을 핵심만 추출하는 것이 추상화인데요. 함수로서의 추상화는 복잡한 로직이 감추어져 있다는 뜻으로, 함수를 받아 처리하기 때문에 단순히 값만 받아 처리하는 함수보다 더 높은 차원의 함수라고 할 수 있습니다. 추상화를 잘 하는 능력은 개발자에게 중요하기 때문에 어떤 조건이든 추상화를 먼저 하는 습관을 들여야겠습니다.
'CODE STATES 44' 카테고리의 다른 글
Styled-Components (0) | 2023.04.18 |
---|---|
CDD와 CSS의 변천사 (0) | 2023.04.18 |
36~40일차 스터디 (1) | 2023.04.16 |
31~35일차 스터디 (0) | 2023.04.16 |
UX/UI로 사용자 친화 웹 제작 (0) | 2023.04.13 |