🔸 JavaScript에서 대표적인 일급 객체 중 하나인 함수는 아래와 같이 특별하게 취급됨.
🔸 변수에 할당(assignment) 가능
🔸 다른 함수의 전달인자(argument)로 전달 가능.
🔸 다른 함수의 결과로써 리턴 가능.
🔸 고차 함수 (higher order function) : 함수를 전달인자(argument)로 받을 수 있고, 함수를 리턴할 수 있는 함수.
🔸 콜백 함수 (callback fuction) : 다른 함수(caller)의 전달인자로 전달되는 함수.
🔸 커링 함수 (curring function) : 함수를 리턴하는 함수 (고차함수가 커링함수를 포함함)
function double(num) {
return num * 2;
}
function doubleNum(func, num) {
return func(num);
}
// 아래와 같은 경우, 함수 double은 함수 doubleNum의 콜백 함수.
let output = doubleNum(double, 4);
console.log(output); // -> 8
// adder는 인자 한 개를 입력받아서 함수(익명 함수)를 리턴
function adder(added) { // 1. added = 5
return function (num) { // 3. num = 3
return num + added; // 2. num + 5; 4. 3 + 5
};
}
let output = adder(5)(3); // -> 8
console.log(output); // -> 8
function double(num) { // 3. num = 5
return num * 2; // 4. 5 * 2 = 10
}
function doubleAdder(added, func) { // 1. added = 5; func = f double
const doubled = func(added); // 2. doubled = double(5) ; 10
return function (num) { // 5. num = 3 //
return num + doubled; // 6. 3 + 10 ; 13 //
};
}
doubleAdder(5, double)(3) // 13
const addTwice3 = doubleAdder(3, double); // 6
addTwice3(2); // 8
// doubleAdder가 리턴하는 함수를변수에 저장할 수 있음.
🔸 JavaScript에는 기본적으로 내장된 고차 함수가 여러개 있으며, 그 중 배열 메서드들 중 일부가 대표적인 고차 함수에 해당함.
🔸 Array.filter(callbackFn)
: 모든 배열의 요소 중에서 특정 조건을 만족하는 요소를 걸러내는 메서드.
callbackFn
: 걸러내는 기준이 되는 특정 조건으로 함수의 형태임. filter
메서드는 함수를 전달인자로 받는 고차 함수.🔸 filter
메서드는 배열의 각 요소를 콜백 함수에 전달.
🔸 콜백 함수는 전달받은 배열의 요소를 받아 함수를 실행하고, 콜백 함수 내부의 조건에 따라 참(true) 또는 거짓(false)을 리턴함.
🔸 조건에 맞는 데이터만 분류(filtering) 할 때 사용.
- 배열의 각 요소가
- 특정 논리(함수)에 따르면,
- 사실(true)일 때 따로 분류함(filter).
// 단행본 모음 const cartoons = [ { id: 1, bookType: 'cartoon', title: '식객', subtitle: '어머니의 쌀', createdAt: '2003-09-09', genre: '요리', artist: '허영만', averageScore: 9.66, }, { id: 2, // .. 이하 생략 }, // ... 이하 생략 ]; // 단행본 한 권의 출판 연도가 2003인지 확인하는 함수 const isCreatedAt2003 = function (cartoon) { const fullYear = new Date(cartoon.createdAt).getFullYear() return fullYear === 2003; }; // 출판 연도가 2003년인 책의 모음 const filteredCartoons = cartoons.filter(isCreatedAt2003);
🔸 map()
: 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환.
🔸 하나의 데이터를 다른 데이터로 매핑(mapping)할 때 사용.
🔸 기존 배열을 변경하지 않음.
- 배열의 각 요소가
- 특정 논리(함수)에 의해
- 다른 요소로 지정됨(map).
// 만화책 모음 const cartoons = [ { id: 1, bookType: 'cartoon', title: '식객', subtitle: '어머니의 쌀', createdAt: '2003-09-09', genre: '요리', artist: '허영만', averageScore: 9.66, }, { id: 2, // .. 이하 생략 }, // ... 이하 생략 ]; // 만화책 한 권의 부제를 리턴하는 로직(함수) const findSubtitle = function (cartoon) { return cartoon.subtitle; }; // 각 책의 부제 모음 const subtitles = cartoons.map(findSubtitle); // ['어머니의 쌀', ...]
🔸 Array.reduce(callback[, initialValue])
: 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환.
initialValue
: callback
의 최초 호출에서 첫 번째 인수에 제공하는 값. 초기값을 제공하지 않으면 배열의 첫 번째 요소를 사용함.🔸 여러 데이터를, 하나의 데이터로 응축(reduce)할 때 사용.
🔸 리듀서(reducer) 함수는 네 개의 인자를 가짐.
initialValue
를 제공한 경우에는 initialValue
의 값.initialValue
를 제공한 경우 0, 아니면 1부터 시작)function (accumulator, currentValue, currentIndex, array) {
return accumulator + currentValue;
}
🔸 리듀서 함수의 반환 값은 누산기(acc)에 할당.
- 배열의 각 요소를
- 특정 방법(함수)에 따라
- 원하는 하나의 형태로
- 응축합니다. (reduction)
// 단행본 모음 const cartoons = [ { id: 1, bookType: 'cartoon', title: '식객', subtitle: '어머니의 쌀', createdAt: '2003-09-09', genre: '요리', artist: '허영만', averageScore: 9.66, }, { id: 2, // .. 이하 생략 }, // ... 이하 생략 ]; // 단행본 한 권의 평점을 누적값에 더한다. const scoreReducer = function (sum, cartoon) { return sum + cartoon.averageScore; }; // 초기값에 0을 주고, 숫자의 형태로 평점을 누적한다. let initialValue = 0 // 모든 책의 평점을 누적한 평균을 구한다. const cartoonsAvgScore = cartoons.reduce(scoreReducer, initialValue) / cartoons.length;
🔸 배열을 문자열로
- 유저 정보 배열 안에 있는 요소의 이름을 하나로 응축
function joinName(resultStr, user) { resultStr = resultStr + user.name + ', '; return resultStr; } let users = [ { name: 'Tim', age: 40 }, { name: 'Satya', age: 30 }, { name: 'Sundar', age: 50 } ]; users.reduce(joinName, ''); // 'Tim, Satya, Sundar'
호출 횟수 resultStr user 리턴 값 1번째 호출 ''
'Tim'
'Tim'
2번째 호출 'Tim'
'Satya'
'Tim, Satya'
n번째 호출 'Tim, Satya'
'Sundar'
'Tim, Satya, Sundar'
🔸 배열을 객체로
- 유저 정보 배열 안에 있는 요소로 주소록을 만듦.
function makeAddressBook(addressBook, user) { let firstLetter = user.name[0]; if(firstLetter in addressBook) { addressBook[firstLetter].push(user); } else { addressBook[firstLetter] = []; addressBook[firstLetter].push(user); } return addressBook; } let users = [ { name: 'Tim', age: 40 }, { name: 'Satya', age: 30 }, { name: 'Sundar', age: 50 } ]; users.reduce(makeAddressBook, {}); // { T: [{ name: 'Tim', age: 40}], S: [ { name: 'Satya', age: 30 }, { name: 'Sundar', age: 50 }] }
호출 횟수 addressBook user 리턴 값 1번째 호출 {}
{ name: 'Tim', age: 40 }
{T: [{ name: 'Tim', age: 40 }]}
2번째 호출 {T: [{ name: 'Tim', age: 40 }]}
{ name: 'Satya', age: 30 }
{T: [{ name: 'Tim', age: 40 }], S: [{ name: 'Satya', age: 30 }]}
n번째 호출 {T: [{ name: 'Tim', age: 40 }], S: [{ name: 'Satya', age: 30 }]}
{ name: 'Sundar', age: 50 }
{T: [{ name: 'Tim', age: 40 }], S: [{ name: 'Satya', age: 30 }, { name: 'Sundar', age: 50 }]}
🔸 추상화(abstraction) : 복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것으로 추상화로 인해 생산성(producticity)이 향상됨.
예시)
브라우저 창에 주소를 입력했을 때, 입력한 내용을 전파하고, 어디 서버로 갔다가 다른 서버로 가는 등 그런 복잡한 내용을, 일상생활에서는 몰라도 됨. 우리는 그저 주소창에 올바른 주소를 입력하면, 브라우저가 해당 사이트를 보여 준다는 것만 알고 있음. 이런 것이 추상화.
🔸 자주 반복해서 사용하는 로직은 별도의 함수로 작성는 것은 추상화의 사례로 볼 수 있음. 추상화의 관점에서 함수를 바라보면, 함수는 사고(thought) 또는 논리(logic)의 묶음.
🔸 함수를 통해 얻은 추상화를 한 단계 더 높인 것이 고차함수.
🔸 고차 함수는 이 추상화의 수준을 사고의 추상화 수준으로 끌어올림.
🔸 고차 함수를 통해, 보다 높은 수준(higher order)에서 생각할 수 있음.
const data = [
{
gender: 'male',
age: 24,
},
{
gender: 'male',
age: 25,
},
{
gender: 'female',
age: 27,
},
{
gender: 'female',
age: 22,
},
{
gender: 'male',
age: 29,
},
];
function getAverageAgeOfMale(data) {
const onlyMales = data.filter(function (d) {
return d.gender === "male"
});
const numOfMales = onlyMales.length
const onlyMaleAges = onlyMales.map(function (d) {
return d.age;
})
const sumOfAges = onlyMales.reduce(function (acc, cur) {
return acc + cur
}, 0)
return sumOfAges / numOfMales;
}
getAverageAgeOfMale
함수는 배열 메서드(filter
, map
, reduce
)를 적절하게 사용하여 순차적으로 원하는 작업을 수행.'male'
을 매개변수화 하여 조금 더 일반적인 함수로 변경할 수 있음. function getOnlyMales(data) {
return data.filter(function (d) {
return d.gender === 'male';
});
}
function getOnlyAges(data) {
return data.map(function (d) {
return d.age;
});
}
function getAverage(data) {
const sum = data.reduce(function (acc, cur) {
return acc + cur;
}, 0);
return sum / data.length;
}
function compose(...funcArgs) {
// compose는 여러 개의 함수를 인자로 전달받아 함수를 리턴하는 고차 함수입니다.
// compose가 리턴하는 함수(익명 함수)는 임의의 타입의 data를 입력받아,
return function (data) {
// funcArgs의 요소인 함수들을 차례대로 적용(apply)시킨 결과를 리턴합니다.
let result = data;
for (let i = 0; i < funcArgs.length; i++) {
result = funcArgs[i](result);
}
return result;
};
}
// compose를 통해 함수들이 순서대로 적용된다는 것이 직관적으로 드러납니다.
// 각각의 함수는 다른 목적을 위해 재사용(reuse) 될 수 있습니다.
const getAverageAgeOfMale = compose(
getOnlyMales, // 배열을 입력받아 배열을 리턴하는 함수
getOnlyAges, // 배열을 입력받아 배열을 리턴하는 함수
getAverage // 배열을 입력받아 `number` 타입을 리턴하는 함수
);
const result = getAverageAgeOfMale(data);
console.log(result); // --> 26