고차함수란
고차함수는, 함수를 인자로 전달
받거나, 함수를 결과로 반환
하는 함수이다.
- 여기서 함수를 결과를 반환하게 되면, 그것은 클로저로 동작한다.
고차함수는, 어떻게 할지의 '절차' 보다는 '무엇을 할지'에 초점을 맞춘다.
자바스크립트에서는 map, filter, forEach, reduce 등 무엇을 할지에 대해 초점을 맞춘 메서드들이 있는데,
이 메서드들에서 break, continue를 사용할 수 없다.
-> break, continue는 '절차'를 스킵하거나 /강제 종료하기 위한 명령어이기 때문.
즉 고차함수 메서드들은 원하는 결과를 얻기 위한 '과정'을 중요시한다.
반대로 절차를 중요시하는 대표적인 예로는 반복문이 있는데
반복문은 '무엇을 할지'가 아니라 '어떻게 할지'에 초점을 맞춘다.
그러므로 초기값, 멈추는 기준, 증가시킬 iterator의 총 3가지 값을 요구하며
때때로 절차를 무시할 수 있는 break, continue의 도움을 받는 것이다.
아무튼, 이제 고차함수를 직접 구현해보고 배열에 내장된 고차함수들을 알아보자.
고차함수 구현하기
자바스크립트에서 일급객체인 함수는 단순한 "값"
취급된다.
그러므로 매개변수로 전달하거나 결과값으로 반환하는 등 '값'으로 할 수 있는 모든 역할에 대입할 수 있다.
간단한 예시를 보면
function validateUser(userName) {
if (findUser(userName)) return true;
else return false;
}
function validateManager(managerName) {
if (findManager(managerName)) return true;
else return false;
}
다음의 두 함수는, 각각 특정 로직을 통해 유저인지 / 매니저인지를 각각 검증한다.
그러나 findUser, findManager 함수만 다를 뿐 validate 함수의 내부 로직은 매우 유사하다.
이럴 때 고차함수의 성질을 이용한다면 함수 자체를 매개변수로 넘겨서 더 간단하게 표현이 가능하다.
function validateWithFindFunc(name, findFunc) {
if (findFunc(name)) return true;
else return false;
}
validateWithFindFunc('userName', findUser);
validateWithFindFunc('managerName', findManager);
다음과 같이 함수를 매개변수로 넘겨서, validateWithFindFunc 함수 내부에서 사용함으로서
로직이 유사한 두 함수를 하나로 통합시킬 수도 있다.
이외에도, 절차지향적으로 표현한 코드를 고차함수를 이용해서 더 간단하게 표현할 수도 있는데
먼저 절차지향적인 for를 이용하면
const arr = ['Tom', 'Jane', 'Bob'];
const arr2 = [];
for(let i=0; i<arr.length; i++){
arr2.push(arr[i] + ' is my friend.');
};
다음의 코드를 고차함수를 이용하면
const arr = ['Tom', 'Jane', 'Bob'];
const arr2 = arr.map(elem => elem + ' is my friend.');
더 간단하게 표현이 가능하다.
배열 내장 메서드에서의 고차함수
고차함수들은 배열에 내재된 메소드들에서도 찾을 수 있다.
map, reduce, filter는 워낙 유명하니 그 외 다른 메소드들을 하나씩 살펴보겠다.
- Array.prototype.forEach
const print = (n) => console.log(n);
[print, print, print, print].forEach((fn, i) => fn(i));
// 0 1 2 3 이 출력된다.
배열을 순회하는 for문과 유사하게 동작한다.
그러나, forEach 문에서는 break, continue를 사용할 수 없으며
반드시 모든 처음부터 끝까지 모든 원소를 순회한다.
사실 for문에 비해 성능이 좋지 않지만, 가독성이 좋으므로 사용할 수 있는 상황에서는 사용하는 것이 좋다.
- Array.prototype.find
const player = [
{name : 'Tom', age : '12'},
{name : 'Bob', age : '23'},
{name : 'Jane', age : '12'},
];
const findBob = (player) => player.name === 'Bob';
player.find(pl=>pl.name === 'Bob');
player.find(findBob);
// {name: 'Bob', age: '23'}
find는 배열에서 콜백 함수를 true로 리턴하는 첫 결과에 해당하는 인수를 보여준다.
- Array.prototype.some
const player = [
{name : 'Tom', age : '12'},
{name : 'Bob', age : '23'},
{name : 'Jane', age : '12'},
];
const findBob = (player) => player.name === 'Bob';
player.some(findBob);
// true
player.some(pl => pl.name === 'Crong');
// false
some 메서드는 배열에서 콜백함수를 통과하는 요소가 하나라도 있다면, true를 반환한다.
- Array.every
const player = [
{name : 'Tom', age : '12'},
{name : 'Bob', age : '23'},
{name : 'Jane', age : '12'},
];
const findBob = (player) => player.name === 'Bob';
player.every(findBob);
// false
const isObject = elem => typeof elem === 'object';
player.every(isObject);
// true
every 메서드는 배열의 요소가 콜백함수를 모두 통과해야(모두 true여야) true를 반환한다.
findBob의 경우는 Bob만 통과되기 때문에 false를 반환하고, isObject의 경우엔 모든 요소가 객체에 해당하므로 true를 반환한다.
- Array.prototype.flatMap
이름에서 알 수 있듯이, map 메서드와 연관되어 있다.
flatMap은 map 함수 결과에 flat() 함수를 적용한 결과를 리턴하는데,
const fruits = ['banana', 'kiwi', 'apple', 'melon'];
fruits.map((fruit, idx)=>[fruit, idx]);
/*
[['banana', 0],
['kiwi', 1],
['apple', 2],
['melon', 3]
]
*/
fruits.flatMap((fruit, idx)=>[fruit, idx]);
// ['banana', 0, 'kiwi', 1, 'apple', 2, 'melon', 3]
map 함수의 결과에서 1차원을 줄인다고 생각하면 되겠다.
만약 fruits.map과 fruits.flatMap의 결과로 같은 배열을 반환받고 싶다면,
fruits.map((fruit, idx)=>[fruit, idx]);
fruits.flatMap((fruit, idx)=>[[fruit, idx]]);
다음과 같이 배열을 하나 더 감싸주면 된다.
- Array.prototype.reduceRight
이 함수도 이름에서 알 수 있뜻이, reduce와 관련되어 있는데,
기존의 reduce가 배열 첫번째 원소부터 순회하며 누산을 진행한다면
reduceRight는 배열 마지막 원소부터 첫번째 원소로 순회하며 누산을 진행한다.
const order = [1,2,3,4,5,6];
order.reduce((acc,cur)=> acc+cur, ''); // '123456'
order.reduceRight((acc,cur)=>acc+cur, ''); // '654321'
마치며
조건문과 반복문은 고차함수에 비해서 로직 흐름에 대한 이해가 어렵고 가독성을 해치며,
변수의 값을 직접 변경하는 행위는 오류의 직접적인 원인이 되기도 한다.
그러므로 이와 같이 순수함수와 보조함수 조합을 통해, 고차함수를 구현하여 코드를 작성하게 되면
절차지향에서 남발되는 조건문 / 반복문을 제거할 수 있고
코드의 복잡성을 해결하고 변수의 직접적인 조작을 피함으로서 부수효과를 억제할 수 있다.
그러나 데이터 직접적으로 조작하지 않는 것이, 데이터를 생성하지 않는다는 의미가 아니다.
기존 데이터에 변형을 주지 않기 위해, 새로운 데이터를 생성하므로 오히려 메모리에서의 문제가 발생할 수도 있다.
-> 실제로 map, filter, reduce 등의 결과는 기존 배열에 적용되는 것이 아니라 새로운 배열을 리턴하는 형식이니 말이다.
'JavaScript > theory' 카테고리의 다른 글
[자바스크립트] V8 엔진의 메모리 관리 이해하기 (0) | 2022.01.12 |
---|---|
[자바스크립트] 일반 객체 다루기 (0) | 2022.01.11 |
[함수형] 클로저 Closure에 대해 알아보자. (0) | 2022.01.06 |
[객체] 자바스크립트에서 객체를 생성하는 다양한 방법 (0) | 2022.01.04 |
[자바스크립트] 비동기 작업의 동기 처리를 위한 Async/Await (0) | 2021.08.22 |