HOF-고차함수


JavaScript 고차 함수



고차 함수

내부적으로 for문을 돌며, 인수로 콜백함수를 받을 수 있다.

Array.prototype.sort

  • 배열명.sort()
  • 배열의 요소를 적절하게 정렬. (기본적으로 오름차순)
  • 원본 배열을 직접 변경하며 정렬된 배열을 반환.
  • 문자열 요소들로 이루어진 배열의 정렬은 아무런 문제가 없다.
1
2
3
4
5
6
7
8
9
10
11
12
13
const fruits = ['Banana', 'Orange', 'Apple'];

// 오름차순(ascending) 정렬
fruits.sort();

// sort 메소드는 원본 배열을 직접 변경한다.
console.log(fruits); // ['Apple', 'Banana', 'Orange']

// 내림차순(descending) 정렬
fruits.reverse();

// reverse 메소드도 원본 배열을 직접 변경한다.
console.log(fruits); // ['Orange', 'Banana', 'Apple']
  • 숫자 배열은 정렬 시 추가 작업이 필요.

    배열명.sort(function(a,b){return a - b;}); : 숫자 정렬 시 사용. 오름차순으로 정렬한다.
    배열명.sort(function(a,b){return b - a;}); : 숫자 정렬 시 사용. 내림차순으로 정렬한다.

    return 1 : 오름차순

    return 0 : 같다.

    return -1 : 내림차순

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const points = [40, 100, 1, 5, 2, 25, 10];

// 숫자 배열 오름차순 정렬
// 비교 함수의 반환값이 0보다 작은 경우, a를 우선하여 정렬한다.
points.sort((a, b) => { return a - b; });
console.log(points); // [1, 2, 5, 10, 25, 40, 100]

// 숫자 배열에서 최소값 취득
console.log(points[0]); // 1

// 숫자 배열 내림차순 정렬
// 비교 함수의 반환값이 0보다 큰 경우, b를 우선하여 정렬한다.
points.sort((a, b) => { return b - a; });
console.log(points); // [100, 40, 25, 10, 5, 2, 1]

// 숫자 배열에서 최대값 취득
console.log(points[0]); // 100

Array.prototype.forEach

  • 배열명.forEach((item, index, self) => { }); //item: 요소, index: 인덱스, self: this
  • for문의 대체 메소드. (forEach가 가독성이 더 뛰어나다.)
  • 배열을 순회하며 배열의 각 요소에 대하여 인수로 전달된 콜백 함수를 실행.
  • 2번쨰 인수로 this를 전달 받을 수 있다.
  • for처럼 중간에 break, continue로 빠져나갈 수 없다.
  • undefined를 반환함.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const numbers = [1, 2, 3];
let pows = [];

// for 문으로 순회
for (let i = 0; i < numbers.length; i++) {
pows.push(numbers[i] ** 2);
}

console.log(pows); // [1, 4, 9]

pows = [];

// forEach 메소드로 순회
numbers.forEach(item => pows.push(item ** 2));

console.log(pows); // [1, 4, 9]

콜백을 이용하여 직접 변경도 가능하다.

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

// forEach 메소드는 원본 배열(this)을 변경하지 않는다.
// 하지만 콜백 함수가 원본 배열(this)을 변경할 수는 있다.
numbers.forEach((item, index, self) => self[index] = Math.pow(item, 2));

console.log(numbers); // [1, 4, 9]

1
2
3
4
5
6
7
8
9
10
11
12
13
class Numbers {
numberArray = [];

multiply(arr) {
// 화살표 함수 내부에서 this를 참조하면
// 상위 컨텍스트, 즉 multiply 메소드 내부의 this를 그대로 참조한다.
arr.forEach(item => this.numberArray.push(item * item));
}
}

const numbers = new Numbers();
numbers.multiply([1, 2, 3]);
console.log(numbers.numberArray); // [1, 4, 9]

화살표 함수는 내부에 this가 없기 떄문에 자신의 상위 스코프의 this를 가져다가 쓴다.


Array.prototype.map

  • 배열명.map((item, index, self) => { }); //item: 요소, index: 인덱스, self: this
  • 배열을 순회하며 배열의 각 요소에 대하여 인자로 주어진 콜백 함수를 실행한다.
  • map은 요소의 개수만큼 순회하면서 요소를 생성하여 반환한다.
  • 콜백 함수의 반환한 값들이 요소로서 추가된 새로운 배열을 반환한다.
  • 원본 배열은 변경되지 않는다.
  • 희소 배열의 존재하지 않는 요소는 순회 대상에서 제외된다.
  • this를 전달 받을 수 있다.
1
2
3
4
5
6
7
8
9
10
// map 메소드는 전달받은 콜백 함수를 호출하면서 3개(요소값, 인덱스, this)의 인수를 전달한다.
[1, 2, 3].map((item, index, self) => {
console.log(`요소값: ${item}, 인덱스: ${index}, this: ${self}`);
return item;
});
/*
요소값: 1, 인덱스: 0, this: 1,2,3
요소값: 2, 인덱스: 1, this: 1,2,3
요소값: 3, 인덱스: 2, this: 1,2,3
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
const numbers = [1, 4, 9];

// 배열을 순회하며 배열의 각 요소에 대하여 인자로 주어진 콜백 함수를 실행한다.
// 그리고 콜백 함수의 반환한 값들이 요소로서 추가된 새로운 배열을 반환한다.
const roots = numbers.map(item => Math.sqrt(item));

// 위 코드의 축약표현은 아래와 같다.
// const roots = numbers.map(Math.sqrt);

// map 메소드는 새로운 배열을 반환한다
console.log(roots); // [ 1, 2, 3 ]
// map 메소드는 원본 배열은 변경하지 않는다
console.log(numbers); // [ 1, 4, 9 ]

Array.prototype.filter

  • 배열명.filter((item, index, self) => { }); //item: 요소, index: 인덱스, self: this
  • 배열을 순회하며 배열의 각 요소에 대하여 인자로 주어진 콜백 함수를 실행한다.
  • return 조건식에 부합하는 것을 반환한다.
  • 실행 결과가 true인 배열 요소의 값만을 추출한 새로운 배열을 반환
  • 원본 배열은 변경되지 않는다.
  • this를 전달 받을 수 있다.
1
2
3
4
5
6
7
8
9
10
// filter 메소드는 전달받은 콜백 함수를 호출하면서 3개(요소값, 인덱스, this)의 인수를 전달한다.
[1, 2, 3].filter((item, index, self) => {
console.log(`요소값: ${item}, 인덱스: ${index}, this: ${self}`);
return item % 2;
});
/*
요소값: 1, 인덱스: 0, this: 1,2,3
요소값: 2, 인덱스: 1, this: 1,2,3
요소값: 3, 인덱스: 2, this: 1,2,3
*/
1
2
3
4
5
6
const numbers = [1, 2, 3, 4, 5];

// 홀수만을 필터링한다 (1은 true로 평가된다)
const odds = numbers.filter(item => item % 2);

console.log(odds); // [1, 3, 5]

Array.prototype.reduce

  • 배열.reduce( (누적값, 현잿값, 인덱스, 요소) => { return 결과 }, 초깃값);
  • reduce 메소드는 배열을 순회하며 콜백 함수의 이전 반환값과 배열의 각 요소에 대하여 인자로 주어진 콜백 함수를 실행하여 하나의 결과값을 반환한다.
  • 원본 배열은 변경되지 않는다.
  • 첫번째 인수로 콜백 함수 [ 반환값, 요소값, 인덱스, reduce호출한 배열(this)]
  • 두번째 인수는 초기값.
  • reduce 메소드는 배열을 순회하며 단일값을 구해야 하는 경우에 사용한다.
1
2
3
4
// 1부터 4까지 누적을 구한다.
const sum = [1, 2, 3, 4].reduce((pre, cur, index, self) => pre + cur, 0);

console.log(sum); // 10

첫번째 인수로 전달받은 콜백 함수는 4개의 인수를 전달받아 배열의 length만큼 총 4회 호출된다.

이때 콜백 함수로 전달되는 인수와 반환값은 아래와 같다.

구분 콜백 함수에 전달된 인수 콜백 함수의 반환값
pre cur index self
첫번째 순회 0 (초기값) 1 0 [1, 2, 3, 4] 1 (pre + cur)
두번째 순회 1 2 1 [1, 2, 3, 4] 3 (pre + cur)
세번째 순회 3 3 2 [1, 2, 3, 4] 6 (pre + cur)
네번째 순회 6 4 3 [1, 2, 3, 4] 10 (pre + cur

  • 평균 구하기
1
2
3
4
5
6
7
8
9
const values = [1, 2, 3, 4, 5, 6];

const average = values.reduce((pre, cur, i, self) => {
// 마지막 순회인 경우, 누적값으로 평균을 구해 반환
// 마지막 순회가 아닌 경우, 누적값을 반환
return i === self.length - 1 ? (pre + cur) / self.length : pre + cur;
}, 0);

console.log(average); // 3.5
  • 최대값 구하기
1
2
3
4
const values = [1, 2, 3, 4, 5];

const max = values.reduce((pre, cur) => (pre > cur ? pre : cur), 0);
console.log(max); // 5

하지만, Math.max 메소드를 사용하는 방법이 보다 직관적이다. min, max는 가변인자함수형태로 받는다.

1
2
3
4
const values = [1, 2, 3, 4, 5];

const max = Math.max(...values);
console.log(max); // 5
  • 중복된 요소의 개수 구하기
1
2
3
4
5
6
7
8
9
10
11
const fruits = ['banana', 'apple', 'orange', 'orange', 'apple'];

const count = fruits.reduce((pre, cur) => {
// 첫번째 순회: pre => {}, cur => 'banana'
// 빈 객체에 요소값을 프로퍼티 키로 추가하고 프로퍼티 값을 할당
// 만약 프로퍼티 값이 undefined이면 0으로 초기화
pre[cur] = (pre[cur] || 0) + 1;
return pre;
}, {});

console.log(count); // { banana: 1, apple: 2, orange: 2 }
  • 중첩 배열 평탄화
1
2
3
4
5
6
const values = [1, [2, 3], 4, [5, 6]];

const flatten = values.reduce((pre, cur) => pre.concat(cur), []);
// [1] => [1, 2, 3] => [1, 2, 3, 4] => [1, 2, 3, 4, 5, 6]

console.log(flatten); // [1, 2, 3, 4, 5, 6]
  • 중복 요소 제거 ( reduce보다는 filter이용하는게 더 직관적이다. )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const values = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4];

const result = values.reduce((pre, cur, i, self) => {
// 순회중인 요소의 인덱스가 자신의 인덱스라면 처음 순회하는 요소이다.
// 이 요소만 배열에 담아 반환한다.
// 순회중인 요소의 인덱스가 자신의 인덱스가 아니라면 중복된 요소이다.
// 3번째 순회: [1, 2], 1, 2, [1, 2, 1, 3, 5, 4, 5, 3, 4, 4]
// if ([1, 2, 1, 3, 5, 4, 5, 3, 4, 4].indexOf(1) === 2) => if(0 === 2)
if (self.indexOf(cur) === i) pre.push(cur);
return pre;
}, []);

console.log(result); // [1, 2, 3, 5, 4]

// filter 사용 시
const values2 = [1, 2, 1, 3, 5, 4, 5, 3, 4, 4];

// 순회중인 요소의 인덱스가 자신의 인덱스라면 처음 순회하는 요소이다. 이 요소만 반환한다.
const result = values2.filter((v, i, self) => self.indexOf(v) === i);
console.log(result); // [1, 2, 3, 5, 4]

이처럼 map, filter, some, every, find와 같은 모든 배열 고차 함수는 reduce로 구현할 수 있다.

1
2
const sum = [].reduce((pre, cur) => pre + cur);
// TypeError: Reduce of empty array with no initial value

이처럼 빈 배열로 reduce 메소드를 호출하면 에러가 발생한다.
reduce 메소드에 초기값을 전달하면 에러가 발생하지 않는다.

1
2
const sum = [].reduce((pre, cur) => pre + cur, 0);
console.log(sum); // 0
  • 객체의 프로퍼티 값을 합산하는 경우에는 반드시 초기값을 전달해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
const products = [
{ id: 1, price: 100 },
{ id: 2, price: 200 },
{ id: 3, price: 300 }
];

// 1번째 순회 : pre => 0, cur => 100
// 2번째 순회 : pre => 100, cur => 200
// 3번째 순회 : pre => 300, cur => 300
const priceSum = products.reduce((pre, cur) => pre + cur.price, 0);

console.log(priceSum); // 600

Array.prototype.some

  • 배열명.some((item, index, self) => { }); //item: 요소, index: 인덱스, self: this
  • 배열을 순회하며 요소 중 하나라도 콜백 함수의 테스트를 통과하면 true, 모든 요소가 콜백 함수의 테스트를 통과하지 못하면 false를 반환한다.
  • 2번째 인자로 this를 전달 받을 수 있다.
1
2
3
4
5
6
7
8
9
10
11
// 배열의 요소 중에 10보다 큰 요소가 1개 이상 존재하는지 확인
let result = [5, 10, 15].some(item => item > 10);
console.log(result); // true

// 배열의 요소 중에 0보다 작은 요소가 1개 이상 존재하는지 확인
result = [5, 10, 15].some(item => item < 0);
console.log(result); // false

// 배열의 요소 중에 'banana'가 1개 이상 존재하는지 확인
result = ['apple', 'banana', 'mango'].some(item => item === 'banana');
console.log(result); // true

Array.prototype.every

  • 배열명.every((item, index, self) => { }); //item: 요소, index: 인덱스, self: this
  • every 메소드는 배열을 순회하며 모든 요소가 콜백 함수의 테스트를 통과하면 true, 요소 중 하나라도 콜백 함수의 테스트를 통과하지 못하면 false를 반환한다.
  • 2번째 인자로 this를 전달 받을 수 있다.
1
2
3
4
5
6
7
// 배열의 모든 요소가 3보다 큰지 확인
let result = [5, 10, 15].every(item => item > 3);
console.log(result); // true

// 배열의 모든 요소가 10보다 큰지 확인
result = [5, 10, 15].every(item => item > 10);
console.log(result); // false

Array.prototype.find

  • 배열명.find((item, index, self) => { }); //item: 요소, index: 인덱스, self: this
  • 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백 함수를 실행하여 그 결과가 참인 첫번째 요소를 반환한다.
  • 참인 요소가 존재하지 않는다면 undefined를 반환한다.
  • 반드시 첫번쨰 요소만 반환하고 끝난다.
  • 2번째 인자로 this를 전달 받을 수 있다.

아래 예제에서는 프로퍼티 id의 값이 2인 배열 요소가 2개 있다. 그러나 정작 반환하는 것은 name: ‘Kim’ 뿐이다.

1
2
3
4
5
6
7
8
9
10
11
12
const users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' },
{ id: 2, name: 'Choi' },
{ id: 3, name: 'Park' }
];

// id가 2인 요소를 반환한다.
const result = users.find(item => item.id === 2);

// Array#find는 배열이 아니라 요소를 반환한다.
console.log(result); // {id: 2, name: 'Kim'}

filter 메소드는 콜백 함수의 실행 결과가 true인 요소만을 추출한 새로운 배열을 반환한다.

따라서 filter의 반환값은 언제나 배열이다.

하지만 find 메소드는 콜백 함수를 실행하여 그 결과가 참인 첫번째 요소를 반환하므로 find의 결과값은 해당 요소값이다.

1
2
3
4
5
// Array#filter는 배열을 반환한다.
[1, 2, 2, 3].filter(item => item === 2); // -> [2, 2]

// Array#find는 요소를 반환한다.
[1, 2, 2, 3].find(item => item === 2); // -> 2

Array.prototype.findIndex

  • 배열명.findIndex((item, index, self) => { }); //item: 요소, index: 인덱스, self: this
  • 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백 함수를 실행하여 그 결과가 참인 첫번째 요소의 인덱스를 반환한다.
  • 콜백 함수의 실행 결과가 참인 요소가 존재하지 않는다면 -1를 반환한다.
  • 반드시 첫번쨰 요소의 인덱스만 반환하고 끝난다.
  • 2번째 인자로 this를 전달 받을 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' },
{ id: 2, name: 'Choi' },
{ id: 3, name: 'Park' }
];

function predicate(key, value) {
// key와 value를 기억하는 클로저를 반환
return item => item[key] === value;
}

// Array#findIndex는 콜백 함수를 실행하여 그 결과가 참인 첫번째 요소의 인덱스를 반환한다.
// id가 2인 요소의 인덱스를 구한다.
let index = users.findIndex(predicate('id', 2));
console.log(index); // 1

// name이 'Park'인 요소의 인덱스를 구한다.
index = users.findIndex(predicate('name', 'Park'));
console.log(index); // 3

Share