first-class citizen이란 자유롭게 거주하고 일 할 수 있고, 출입국의 자유를 가지며, 투표의 자유를 가지는 시민을 의미한다.
자바스크립트에서도 특별한 대우를 받는 것들이 있는데 이런 것들을 일급 객체(first-class citizen)라고 한다.
일급 객체는 일반적으로 다른 객체들에게 적용 가능한 연산을 모두 지원하는 객체를 뜻한다.
first-class citizen(일급객체)의 조건은 아래와 같다.
함수는 일급객체에 속한다. 이 말은 즉, 함수를 데이터(string, number, boolean, array, object)를 다루듯이 다룰 수 있다는 걸 의미한다. 변수에 저장할 수 있기 때문에 배열의 요소나 객체의 속성값이 될 수 있다.
몇가지 예시를 보며, 함수가 일급객체인지를 확인해보자.
1. 변수에 할당할 수 있다.
객체의 키에 값으로 함수를 할당해줄 수 있다. show
라는 키에 함수를 할당해줬다.
music.show();
로 함수가 실행된다.
let music = {
artist: 'IU',
album: 'lilac',
show: function() {
console.log(`이 ${this.album} 앨범의 가수는 ${this.artist} 입니다.`);
}
}
let call = function callMe() {
console.log('호출 합니다.');
}
setInterval(call, 1000);
let name = 'Jimi';
function getScore () {
let num1 = 2,
num2 = 3;
function add() {
return name + " scored " + (num1 + num2);
}
return add();
}
getScore(); // "Jimi scored 5"
이렇듯 함수는 변수에 저장될 수도 있고, 함수의 인자로 전달되거나 함수 내에서 리턴값으로 사용될 수 있다. 위 특징을 가진 함수로 하나 이상의 함수를 인수로 받거나 함수를 반환하는 고차함수를 만들 수 있다.
고차함수는 또 어떤건지 알아보자🤗
고차 함수는 함수를 인자(argument)로 받거나 함수를 리턴하는 함수를 의미한다.
이 때 다른 함수(caller)의 인자(argument)로 전달되는 함수를 콜백 함수(callback function)라고 한다.
콜백 함수를 전달받은 함수는 이 콜백 함수를 호출(invoke)할 수 있다. caller는 조건에 따라 콜백 함수의 실행 여부를 결정할 수도 있고, 여러 번 실행할 수 있다. 특히 콜백 함수는 어떤 작업이 완료되었을 때 호출되는 경우가 많아서 '답신 전화📞'를 뜻하는 콜백이라는 이름이 붙었다.
함수를 인자로 받는 경우와 리턴하는 경우를 봐보자.
doubleNum
은 다른 함수를 인자로 받는 고차 함수이다.doubleNum
의 첫 번째 인자 func
에 함수가 들어올 경우 func
은 콜백함수이다.function double(num) {
return num * 2;
}
function doubleNum(func, num) {
return func(num);
}
// 아래와 같은 경우, 함수 double은 함수 doubleNum의 콜백 함수이다.
let output = doubleNum(double, 4);
console.log(output); // -> 8
num
인자를 받아서 added
와 더한 값을 리턴한다. function adder(added) {
return function (num) {
return num + added;
};
}
// adder(5)는 함수이므로 함수 호출 연산자 '()'를 사용할 수 있다.
let output = adder(5)(3); // -> 8
console.log(output); // -> 8
아래와 같이 adder()함수를 변수에 할당하고, 그 변수를 output에 할당해서 함수를 호출할 수도 있다.
const add3 = adder(3);
output = add3(2);
console.log(output); // -> 5
func
인자에 double()
이 전달되면 double()
는 콜백함수가 된다.double(added)
를 할당받는다.num
에 double(added)
함수 실행 값을 더한 값이기 때문에 double(added)
가 실행되며 num*2 값이 리턴된다. function double(num) {
return num * 2;
}
function doubleAdder(added, func) {
const doubled = func(added);
return function (num) {
return num + doubled;
};
}
// doubleAdder(5, double)는 함수이므로 함수 호출 기호 '()'를 사용한다.
doubleAdder(5, double)(3); // -> 13
// doubleAdder가 리턴하는 함수를 변수에 저장할 수 있다. (일급 객체 특징)
const addTwice3 = doubleAdder(3, double);
addTwice3(2); // --> 8
자바스크립트에는 기본적으로 내장(built-in)되어 있는 고차 함수들이 있다. 배열 메서드 중 forEach(), find(), filter(), map(), reduce(), sort(), some(), every() 등이 해당된다.
배열의 요소 중 특정 조건을 만족하는 요소들만을 걸러서(filter) 새로운 배열을 만드는 메서드이다.
arr.filter(callback func(element, index, arr));
이때, 특정 조건은 함수 형태로 filter()의 인자로 전달되어야 한다.
filter()를 써서 수(number)를 요소로 갖는 배열 중 짝수만을 걸러내거나, 18 보다 작은 수만을 걸러내는 식입니다. 문자열(string)을 요소로 갖는 배열 중 길이가 10 이하인 문자열만 걸러내거나, 'korea'만 걸러낼 수 있다.
배열의 요소 중 짝수만 뽑아 새배열을 만든다고 한다면 filter()를 쓸 수 있다.
배열의 요소들이 el 인자값으로 들어가며, 반복문 처럼 요소들을 순회하며, 조건식이 true인 요소들을 뽑아 새로운 배열을 만든다.
let arr = [1, 2, 3, 4];
let output = arr.filter(function(el) {
return el % 2 === 0;
});
console.log(output); // ->> [2, 4]
filter()의 동작을 쪼개어 나타내보면 아래와 같다.
배열과 특정 조건을 갖는 함수를 인자로 받아 배열의 요소를 순회하며 함수 실행 결과가 true일 때, 새로운 배열에 push해주고, 최종적으로 새로운 배열을 리턴해준다.
let arr = [1, 2, 3, 4];
// 배열의 filter 메소드는 함수를 인자로 받는 고차 함수이다.
arr.filter = function (arr, func) {
const newArr = [];
for (let i = 0; i < arr.length; i++) {
if (func(arr[i]) === true) {
newArr.push(this[i]);
}
}
return newArr;
};
map()이나 reduce()도 반복문을 내포하고 있는 메서드들로 배열의 요소들을 순회하게 된다.
map()은 배열의 모든 요소에 동일한 조건을 적용한 값을 요소로 갖는 새로운 배열을 반환한다.
이때, 조건은 함수로 작성되어야하며, 기존 배열을 수정하지 않는다.
filter()와 다른게 콜백 함수로 작성한 특정 조건을 모든 요소에 적용시킨 값을 반환한다.
map()은 이렇게 하나의 데이터를 다른 데이터로 맵핑(mapping) 할 때 주로 사용한다.
만화책 식객 27권의 정보가 배열에 담겨져 있다. 각 책의 부제(subtitle)만 담은 배열을 만들고 싶다. 이때 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); // 부제의 모음
reduce()는 배열을 콜백함수를 실행하여 하나의 결과 값으로 반환한다.
arr.reduce(callback func(acc, cur, idx, arr), initialValue)
reduce()는 초기값(initialValue)을 정할 수 있다. 정하지 않으면 배열의 제일 첫번째 요소가 초기값이 된다.
reduce()에는 누적값(accumulator)이란 개념이 나오는데
이 처음 누적값은 초기값이 된다.
그리고 이 메서드의 반환 값이 누적값(acc)가 된다.
현재 처리할 요소(cur)에 call back 함수의 실행 결과가 누적값에 할당되고, 누적값은 순회 중 유지되므로 결국 최종 결과는 하나의 값이 된다.
reduce는 문자열이나 숫자를 합치거나 뺄 수도 있고 제일 작은것 혹은 큰것을 비교할 수도 있고 배열이외에 다른 형태로도 만들 수 있다. 이 외에도 활용도가 높다.
reduce()를 어떻게 사용하는지 예시를 살펴보자.
const arr = [1, 2, 3, 4, 5];
let sum = arr.reduce(function(acc,cur) {
return acc + cur;
});
sum; // 15
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, "
위 코드를 보면 reduce의 콜백 함수로 joinName()이 들어가고, 초기값을 빈문자열('')로 넣어줬다.
joinName()에 처음 resultStr의 인자로 ''과, users 배열이 들어가 실행되어진다. 언제까지? users 배열의 길이만큼
function makeAddressBook(addressBook, user) { //addressBook 초기 인자값 : {} > 배열의 요소들
let firstLetter = user.name[0]; //이름의 첫 문자만 변수에 할당
if(firstLetter in addressBook) { //객체에 firstLetter 키가 존재하면
addressBook[firstLetter].push(user); //키값이 배열이니깐 배열에 push
} else {
addressBook[firstLetter] = []; // 키값은 빈배열
addressBook[firstLetter].push(user); //키값에 push
}
return addressBook;
}
let users = [
{ name: 'Tim', age: 40 },
{ name: 'Satya', age: 30 },
{ name: 'Sundar', age: 50 }
];
users.reduce(makeAddressBook, {});
// {T: Array(1), S: Array(2)}
// S: Array(2)
// 0: {name: "Satya", age: 30}
// 1: {name: "Sundar", age: 50}
// T: Array(1)
// 0: {name: "Tim", age: 40}
for문은 break라는 예약어가 있기 때문에 중간에 반복을 끊어줄 수 있지만 reduce()는 중간에 빠져나올 수 없다.
마지막으로 고차함수를 찾아보며, 단번에 이해할 수 있언던 이미지로 마무리!