본문 바로가기

Programming Language/JavaScript

자바스크립트에서 자주 사용하는 고차 함수(forEach, filter, sort, map, reduce) - 2

일반적으로 반복문을 사용하려면 반복문을 어떻게 실행할지에 초점을 맞춰서 구현하게 된다. 따라서 반복문의 시작점 및 초기값인 counter, 반복문 멈추는 기준이 되는 exit condition과 끝나는 조건에 도달할 때까지 counter를 증가시키는 iterator를 가지게 된다. 

즉, 반복문을 구현하려면 이 세 가지가 모두 필요하다. 

 

이와 반대로 고차 함수를 이용한 반복문은 어떻게 실행할지 보다는 무엇을 할지에 초점을 맞춘다. 각각의 반복을 구체적으로 어떻게 진행할지를 명시하기보다는 원하는 결과를 얻기 위한 과정만을 제시한다. 

 

이 둘의 차이점이 더 와 닿을 수 있도록 앞으로의 예제에서 일반적인 반복문(for, while)과 고차 함수를 이용한 반복문을 둘 다 보여줄 것이다. 

 

그럼 이제, 알고 있으면 편한 고차 함수들을 알아보자. 

자바스크립트에서 자주 사용되는 고차 함수들은 주로 배열에 내재되어 있는 메소드들이다. 

 

forEach( )

배열의 각 원소를 순서대로 모두 돌면서 함수를 실행한다. 반환되는 값은 없다.

EX: 특정 배열에 있는 원소를 다른 배열에 복사.

const fruits = ['watermelon', 'blueberries', 'strawberry', 'raspberry'];
const copyFruits = []

// before
for (let i = 0; i < fruits.length; i++){
	copyFruits.push(fruits[i]);
}

// after
fruits.forEach((fruit) => copyFruits.push(fruit));

for문은 continue나 break로 반복을 제어할 수 있지만 forEach는 throw(예외)를 발생시키지 않으면 중간에 반복을 종료할 수 없다.

하지만 forEach문과 예외 처리를 사용하면 코드가 더 복잡해진다. 그렇기 때문에, 조건을 만족할 때까지만 반복시켜야 한다면 기존의 for문이나 some() 함수를 사용하는 것이 더 적절하다.

 

EX: 배열의 값이 2인 경우만 출력. 비슷하게 배열 중 일부값을 찾아서 뭔가를 수행하고자 하는 상황에 해당한다.

// 예외 처리 없이
const numbers = [1, 2, 3, 4, 5];
numbers.forEach((number) => {
	if (number == 2){
    	console.log(number);
        break;
    }
});

// 예외 처리를 이용한 forEach 반복 종료
const errorBreak = new Error('Break');
const numbers = [1, 2, 3, 4, 5];

try {
	numbers.forEach((number) => {
    	if (number == 2){
        	console.log("number 2!");
            throw errorBreak;
        }
    })
} catch (e) {
	if (e != Break)
    	throw Break;
}

 

filter ( )

콜백 함수에 의해 제공된 테스트를 통과한 모든 원소를 가진 새로운 배열을 반환한다.

EX: 성인인 사람(18세 이상)의 사람만 필터링한다

const people = [
	{ name: 'Peter', age: 16},
    { name: 'Mark', age: 18},
    { name: 'John', age: 27},
    { name: 'Jane', age: 14},
    {name: 'Ester', age: 24}
];
const legalAge = [];

// before
for (let i = 0; i < persons.length; i++){
	if(persons[i].age >= 18){
    	fullAge.push(persons[i];
    }
}

// after
legalAge = persons.filter(person => person.age >= 18);

 

sort( )

지정된 함수에 따른 순서로 배열을 정렬한다. 함수가 지정되지 않았을 때에는 배열의 원소들을 문자로 반환해서 유니코드에 따라서 정렬한다.

EX: 문자열이 들어있는 배열을 정렬한다.

const fruits = ["strawberry", "watermelon", "orange", "blueberry", "banana", "apple"];
fruits.sort();
console.log(fruits);
// ["apple", "banana", "blueberry", "orange", "strawberry", "watermelon"]

const numbers = [1, 30, 4, 21, 10000, 225, 335];
numbers.sort();
console.log(numbers);
// [1, 1000, 21, 225, 30, 335, 4]

sort() 함수는 원소들을 유니코드로 전환시켜 정렬하기 때문에 숫자로 이루어진 배열을 정렬할 때는 정렬하는 기준을 정해주는 함수를 지정해줘야 한다. 

 

정렬 방법을 지정하는 함수는 다음과 같은 형식을 가지고 있어야 한다.

function compare(a, b) {
	if (a가 어떠한 기준에 의해서 b보다 작을 경우)
    	return -1;
    if (a가 어떠한 기준에 의해서 b보다 클 경우)
    	return 1;
    //둘이 같을 경우
    return 0;
}

-1와 1 값이 아니어도 되고 양수나 음수의 값을 반환해도 된다. 

 

EX: 여러 정렬 함수를 이용한 배열 정렬.

// sort numbers asc
function compareNumbers(a, b) {
	return a - b;
}
const numbers = [4, 2, 1, 5, 3];
numbers.sort(compareNumbers);
console.log(numbers); 		// [1, 2, 3, 4, 5]

// sort numbers desc
function compareNumbersDesc(a, b){
	return b - a;
}
numbers.sort(compareNumbersDesc);
console.log(numbers);		// [5, 4, 3, 2, 1]

// sort people by name
function sortByName (a, b) {
	if (a.name < b.name)
    	return -1;
   	if (a.name > b.name)
    	return 1;
    return 0;
}
const people = [
	{ name: 'Peter', age: 16},
    { name: 'Mark', age: 18},
    { name: 'John', age: 27},
    { name: 'Jane', age: 14},
    {name: 'Ester', age: 24}
];
people.sort(sortByName);
console.log(people);
// [{name: 'Ester', age: 24}, 
//  {name: 'Jane', age: 14}, 
//  {name: 'John', age: 27}, 
//  {name: 'Mark', age: 18}, 
//  {name: 'Peter', age: 16}]

 

map ( )

배열의 각 원소별로 지정된 함수를 실행한 결과로 새로운 배열을 반환한다. 

forEach와 달리, 함수를 실행한 원소의 결과를 새로운 배열에 저장해서 반환한다. 

EX: 배열의 각 원소를 2와 곱한다

const numbers = [2, 5, 7, 4, 3];

// before
let numbersDouble = [];
for (let i = 0; i < numbers.length; i++) {
	numbersDouble.push(numbers[i] * 2);
}

// after
const numbersDouble = numbers.map((number) => number * 2);

console.log(numbersDouble);		// [4, 10, 14, 8, 6]

 

EX: 사람들의 성과 이름을 하나의 문자열로 합친다

const people = [
{first: "Ester", last: "Kim"},
{first: "Hans", last: "Yoo"},
{first: "Celia", last: "Lee"},
{first: "Ben", last: "Whittaker"},
{first: "Harry", last: "Hart"}
]

function getFullName (person) {
	return person.first + " " + person.last;
}

const fullNames = people.map(getFullName);

console.log(fullNames);
//["Ester Kim", "Hans Yoo", "Celia Lee", "Ben Whittaker", "Harry Hart"]

 

reduce ( )

배열의 각 원소별로 지정된 함수를 실행해서 누적된 하나의 결과를 반환한다. 

map()와 달리, 배열이 아닌, 하나의 값을 반환한다. 

 

함수 reduce는 두 인자(실행할 함수와 초기 값)를 받는다. 실행할 함수는 필수적으로 지정해야 하지만 초기 값은 선택이다. 초기 값이 없는 경우 배열의 첫 원소가 초기 값이 된다.

EX: 배열의 원소들의 총 합을 구한다

const numbers = [2, 5, 7, 4, 3];

// before
let total = 0;
for (let i = 0; i < numbers.length; i++) {
	total += numbers[i];
}

// after
const total = numbers.reduce((accumulator, currentValue) => accumulator + currentValue);

console.log(total);		// 21

 

EX: 3번 째 원소를 제외한 총합을 구한다(인덱스를 사용해야 하는 상황)

const numbers = [2, 5, 7, 4, 3];

// before
let total = 0;
for (let i = 0; i < numbers.length; i++) {
	if (i == 2)
    	continue;
	total += numbers[i];
}

// after
function addExcept3 (accumulator, currentValue, index) {
	if (index == 3)
    	return accumulator;
    return accumulator + currentValue;
}
const total = numbers.reduce(addExcept3);

console.log(total);		// 17

 

지정한 함수에 세 번째 인자로 인덱스에 접근할 수 있다. reduce()를 사용할 때 명시할 점은 매 iteration마다 값을 반환해야지 누적된 값을 유지할 수 있다는 것이다.

 

아래처럼 index가 3이 아닐 경우에만 값을 반환하면 원하는 값을 얻지 못한다.

const numbers = [2, 5, 7, 4, 3];
function addExcept3 (accumulator, currentValue, index) {
	if (index != 3)
    	return accumulator + currentValue;
}
const total = numbers.reduce(addExcept3);

console.log(total);		// NaN

 

EX: 사람들의 나이의 합을 구한다 (초기 값을 지정해야 하는 상황)

const people = [
	{ name: 'Peter', age: 16},
    { name: 'Mark', age: 18},
    { name: 'John', age: 27},
    { name: 'Jane', age: 14},
    {name: 'Ester', age: 24}
];
const totalAge = people.reduce((accumulator, currentAge) => accumulator + currentAge.age, 0);
console.log(totalAge);	// 99

 

만약 위의 예제 코드처럼 초기 값이 0을 지정해주지 않는다면 배열의 첫 원소가 초기 값이 된다. 따라서 첫 accumulator의 값은 객체가 되고 이후에 나이를 추가하면 문자열로 반환되어서 추가된다.

const people = [
	{ name: 'Peter', age: 16},
    { name: 'Mark', age: 18},
    { name: 'John', age: 27},
    { name: 'Jane', age: 14},
    {name: 'Ester', age: 24}
];
const totalAge = people.reduce((accumulator, currentAge) => accumulator + currentAge.age);
console.log(totalAge);	// [object Object]18271424

 

요약

 

 

References

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

 

yuddomack.tistory.com/entry/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Array-forEach

blog.outsider.ne.kr/847

devowen.com/277

velog.io/@jakeseo_me/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%9D%BC%EB%A9%B4-%EC%95%8C%EC%95%84%EC%95%BC-%ED%95%A0-33%EA%B0%80%EC%A7%80-%EA%B0%9C%EB%85%90-22-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EA%B3%A0%EC%B0%A8-%ED%95%A8%EC%88%98Higher-Order-Function-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0

velog.io/@rememberme_jhk/JS-%EA%B3%A0%EC%B0%A8%ED%95%A8%EC%88%98-higher-order-function