- 일급 객체(first-class citizen)의 세 가지 특징을 설명할 수 있다.
- 고차 함수(higher-order function)에 대해 설명할 수 있다.
- 고차 함수를 자바스크립트로 작성할 수 있다.
- 배열 내장 고차 함수 filter에 대해서 이해할 수 있다.
- filter에 대한 이해를 기반으로, 나머지 내장 고차 함수를 학습할 수 있다.
- filter, map, reduce, forEach, find, sort, some, every
- 고차 함수를 쓰는 이유를 설명할 수 있다.
- 고차 함수를 활용하여 프로그램을 작성할 수 있다.
- 추상화에 대해 설명할 수 있다.
- 추상화의 관점에서 고차 함수가 갖는 이점에 대해 설명할 수 있다.
- 고차 함수를 통해 사고 수준에서의 추상화를 달성할 수 있다.
JavaScript에는 비행기의 first-class 좌석처럼 특별한 대우를 받는 일급 객체(first-class citizen) 가 있다. 대표적인 일급 객체 중 하나가 함수.
JavaScript에서 함수는 아래와 같이 특별하게 취급된다.
즉 ,함수를 데이터(string
, number
, boolean
, array
, object
)처럼 다룰 수 있다.
함수를 전달인자(argument)로 받을 수 있고, 함수를 리턴할 수 있는 함수
이때 다른 함수(caller)의 전달인자(argument)로 전달되는 함수를
콜백 함수(callback function)라고 한다.
"커링 함수" 란? ➡ 함수를 리턴하는 함수
//함수 adder는 다른 함수를 리턴하는 고차 함수 function adder(added) { return function (num) { return num + added; }; } // adder(5)는 함수이므로 함수 호출 연산자 '()'를 사용할 수 있다. let output = adder(5)(3); // -> 8 // javascript에서 함수는 일급 객체이기 때문에, //adder가 리턴하는 함수를 변수에 저장할 수 있다 const add3 = adder(3); output = add3(2);
JavaScript에는 기본적으로 내장된 고차 함수가 여럿 있다!
그중에서 배열 메서드들 중 일부가 대표적인 고차 함수에 해당
filter
, map
, reduce
가 있다. 지금부터 하나씩 알아보자!
모든 배열의 요소 중에서 특정 조건을 만족하는 요소를 걸러내는 메서드
걸러내는 기준인 특정 조건은 함수의 형태로, filter
메서드의 전달인자로 전달
➡ 즉, 걸러내기 위한 조건을 명시한 함수를 전달인자로 받기 때문에 고차 함수
filter
메서드는 배열의 요소를 콜백 함수에 다시 전달 ➡ 콜백 함수는 전달받은 배열의 요소를 받아 함수를 실행하고, 콜백 함수 내부의 조건에 따라 참(true) 또는 거짓(false)을 리턴
// 함수 표현식 const isEven = function (num) { return num % 2 === 0; }; let arr = [1, 2, 3, 4]; // '짝수'를 판별하는 함수가 조건으로서 filter 메서드의 전달인자로 전달됨 let output = arr.filter(isEven); console.log(output); // ->> [2, 4]
하나의 데이터를 다른 데이터로 매핑(mapping) 할 때 사용
어떤 배열에 있는 모든 요소들의 값을 변경해서 만든 새로운 배열을 써야 할 때
let users = [ {firstName : "Susan", lastName: "Steward"}, {firstName : "Daniel", lastName: "Longbottom"}, {firstName : "Jacob", lastName: "Black"} ]; let userFullnames = users.map(function(element){ return `${element.firstName} ${element.lastName}`; }) console.log(userFullnames); // ["Susan Steward", "Daniel Longbottom", "Jacob Black"]
여러 데이터를, 하나의 데이터로 응축(reduce)할 때 사용
배열의 각 값에 함수를 적용한 누적 값을 반환
초기값이 있으면 배열의 첫번째 값이 바로 현재 값(초기값에 누적해 주는 값)이되고, 초기값이 없다면 배열의 첫번째 값이 초기값이 된다.
// 단행본 모음 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;
const addAccCur = function (acc, cur) { return acc + cur; }; const initialValue = 1; let arr = [1, 2, 3, 4, 5, 6, 7]; let output = arr.reduce(addAccCur, initialValue); // 만약 initialValue가 없다면 arr.reduce(addAccCurr) 라고 쓰고 // arr의 첫번째 요소인 1이 initialValue가 된다. console.log(output); // -> 29 // initialValue 없을 경우 28
//콜백 함수 joinName은 users 배열 안에 있는 요소의 이름을 하나로 응축 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,
// 콜백 함수 makeAddressBook은 users 배열 안에 있는 요소로 주소록을 만듬 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 }] } */
복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것
JavaScript를 비롯한 많은 프로그래밍 언어 역시, 추상화의 결과이다!
➡ 컴퓨터를 구성하는 장치(중앙처리장치, CPU; Central Processing Unit)는 0과 1만 이해한다
하지만 우리가 0과 1이 아닌, 인간의 언어로 코드를 짜고, 그걸 실행했을때 원하는 결과 값을 얻을 수 있다. 이는 모두 그 복잡한 과정을 JavaScript 해석기(엔진)가 대신해 주기 때문!
즉, 컴퓨터의 내부 구조에 대한 고민이 추상화로 해결된 것.
이로 인해 우리는 JavaScript의 문법(syntax)을 올바르게 사용하는 것만으로, JavaScript가 없었을 때보다 다양한 프로그램을 보다 쉽게 작성할 수 있다.
자주 반복해서 사용하는 로직은 별도의 함수로 작성하는 것 역시 추상화
➡ 값 수준의 추상화: 단순히 값(value)을 전달받아 처리하는 수준
함수를 통해 얻은 추상화를, 한 단계 더 높인 것이 고차 함수
➡ 사고의 추상화: 함수(사고의 묶음)를 전달받아 처리하는 수준
즉, 고차 함수를 통해, 보다 높은 수준(higher order)에서 생각할 수 있다
➡ 이처럼 고민거리가 줄어들고, 그래서 문제의 해결이 더 쉬워지는 것이 추상화의 이점. 즉 추상화 = 생산성(productivity)의 향상 이라고 볼 수 있다.
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