CodeStates/JavaScript
내장 고차 함수 메서드
yeeendy
2023. 5. 11. 00:04
filter
- 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출
- 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환
- 원본 배열은 변경되지 않는다
const numbers = [1, 2, 3, 4, 5];
const odds = numbers.filter(item => item % 2); //홀수냐 //2로 나눈 나머지를 반환
console.log(odds); // [1, 3, 5]
- 자신을 호출한 배열에서 필터링 조건을 만족하는 특정 요소만 추출하여 새로운 배열을 만들고 싶을 때 사용
- filter 메서드의 콜백 함수는 filter 메서드를 호출한 배열의 요소값과 인덱스, filter 메서드를 호출한 배열 자체, 즉 this를 순차적으로 전달받을 수 있다.
- 다시 말해, filter 메서드는 콜백 함수를 호출할 때 3개의 인수, 즉 filter 메서드를 호출한 배열의 요소값과 인덱스, filter 메서드를 호출한 배열(this)을 순차적으로 전달한다.
//요소값, 인덱스, this의 인수를 전달
[1, 2, 3].filter((item, index, arr) => {
console.log(`요소값: ${item}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`);
return item % 2;
});
/*
요소값: 1, 인덱스: 0, this: [1, 2, 3]
요소값: 2, 인덱스: 1, this: [1, 2, 3]
요소값: 3, 인덱스: 2, this: [1, 2, 3]
*/
- filter 메서드의 두 번째 인수로 filter 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
- 자신을 호출한 배열에서 특정 요소를 제거하기 위해 사용할 수도 있다.
class Users {
constructor(){
this.users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' }
];
}
//요소 추출
findById(id){
return this.users.filter(user => user.id === id);
}
//요소 제거
remove(id){
this.users = this.users.filter(user => user.id !== id);
}
}
const users = new Users();
let user = users.findById(1);
console.log(user); // [{ id: 1, name: 'Lee' }]
users.remove(1);
user = users.findById(1);
console.log(user); // []
- 특정 요소를 제거할 경우 특정 요소가 중복되어 있다면 중복된 요소가 모두 제거된다
map
- 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출
- 콜백 함수의 반환값들로 구성된 새로운 배열을 반환
- 원본 배열은 변경되지 않는다
const numbers = [1, 4, 9];
const roots = numbers.map(item => Math.sqrt(item));
//const roots = numbers.map(Math.sqrt)과 동일
console.log(roots); // [1, 2, 3]
console.log(numbers); // [1, 4, 9]
- 요소값을 다른 값으로 매핑한 새로운 배열을 생성하기 위한 고차 함수
- map 메서드가 생성하여 반환하는 새로운 배열의 length 프로퍼티 값은
map 메서드를 호출한 배열의 length 프로퍼티 값과 반드시 일치
즉, map 메서드를 호출한 배열과 map 메서드가 생성하여 반환한 배열은 1:1 매핑 - map 메서드의 콜백 함수는 map 메서드를 호출한 배열의 요소값과 인덱스, map 메서드를 호출한 배열 자체, 즉 this를 순차적으로 전달받을 수 있다.
- 다시 말해, map 메서드는 콜백 함수를 호출할 때 3개의 인수, 즉 map 메서드를 호출한 배열의 요소값과 인덱스, map 메서드를 호출한 배열(this)을 순차적으로 전달한다.
//요소값, 인덱스, this의 인수를 전달
[1, 2, 3].map((item, index, arr) => {
console.log(`요소값: ${item}, 인덱스: ${index}, this: ${JSON.stringify(arr)}`);
return item;
});
/*
요소값: 1, 인덱스: 0, this: [1, 2, 3]
요소값: 2, 인덱스: 1, this: [1, 2, 3]
요소값: 3, 인덱스: 2, this: [1, 2, 3]
*/
- map 메서드의 두 번째 인수로 map 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
class Prefixer {
constructor(prefix){
this.prefix = prefix;
}
add(arr){
return arr.map(function(item){
//외부에서 this를 전달하지 않으면 this는 undefined를 가리킨다
return this.prefix + item;
}, this); //map 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달
}
}
const prefixer = new Prefixer('-webkit-');
console.log(prefixer.add(['transition', 'user-select']));
// ['-webkit-transition', '-webkit-user-select']
reduce
- 자신을 호출한 배열을 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출
- 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫 번째 인수로 전달하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환
- 원본 배열은 변경되지 않는다
- 첫 번째 인수로 콜백 함수, 두 번째 인수로 초기값을 전달
- 4개의 인수
-> 초기값 또는 콜백 함수의 이전 반환값
-> reduce 메서드를 호출한 배열의 요소값과 인덱스
-> reduce 메서드를 호출한 배열 자체, 즉 this
const sum = [1, 2, 3, 4].reduce((acc, cur, index, arr) => acc + cur, 0);
console.log(sum); // 10
//mdn 예제
const array1 = [1, 2, 3, 4];
const initialValue = 0;
const sumWithInitial = array1.reduce(
(accumulator, currentValue) => accumulator + currentValue,
initialValue
);
console.log(sumWithInitial); // 10
sort
- 배열의 요소를 정렬
- 원본 배열을 직접 변경하며 정렬된 배열을 반환
- 기본적으로 오름차순으로 요소를 정렬
const fruits = ['Banana', 'Orange', 'Apple'];
fruits.sort();
console.log(fruits); // ['Apple', 'Banana', 'Orange']
//한글 문자열도 오름차순으로 정렬
const fruits = ['바나나', '오렌지', '사과'];
fruits.sort();
console.log(fruits); // ['바나나', '사과', '오렌지']
- 내림차순으로 요소를 정렬하기
-> sort메서드로 정렬 후 reverse 메서드 사용
-> reverse도 원본 배열 직접 변겨
const fruits = ['Banana', 'Orange', 'Apple'];
fruits.sort();
console.log(fruits); // ['Apple', 'Banana', 'Orange']
fruits.reverse();
console.log(fruits); // ['Orange', 'Banana', 'Apple']
- 숫자 요소로 이루어진 배열을 정렬할 때는 주의가 필요
const points = [40, 100, 1, 5, 2, 25, 10];
points.sort();
console.log(points); // [1, 10, 100, 2, 25, 40, 5]
- sort 메서드의 기본 정렬 순서는 유니코드 코드 포인트의 순서를 따른다
- 배열의 요소가 숫자 타입이라고 할 지라도 배열의 요소를 일시적으로
문자열로 변환한 후 유니코드 코드 포인트의 순서를 기준으로 정렬 - 따라서 숫자 요소를 정렬할 때는 sort 메서드에 정렬 순서를 정의하는 비교 함수를 인수로 전달해야 함
const points = [40, 100, 1, 5, 2, 25, 10];
//숫자 배열의 오름차순 정렬. 비교 함수의 반환값이 0보다 작으면 a를 우선하여 정렬
points.sort((a, b) => a - b);
console.log(points); // [1, 2, 5, 10, 25, 40, 100]
//숫자 배열에서 최소/최대값 취득
console.log(points[0], points[points.length-1]); // 1 100
//숫자 배열의 내림차순 정렬. 비교 함수의 반환값이 0보다 작으면 b를 우선하여 정렬
points.sort((a, b) => b - a);
console.log(points); // [100, 40, 25, 10, 5, 2, 1]
//숫자 배열에서 최소/최대값 취득
console.log(points[points.length-1], points[0]); // 100 1
- 객체를 요소로 갖는 배열을 정렬
const todos = [
{ id: 4, content: 'JavaScript' },
{ id: 1, content: 'HTML' },
{ id: 2, content: 'CSS' },
];
//비교 함수
function compare(key) {
// 프로퍼티 값이 문자열인 경우 - 산술 연산으로 비교하면 NaN이 나오므로 비교 연산을 사용
// 비교 함수는 양수/음수/0을 반환하면 되므로 - 산술 연산 대신 비교 연산을 사용
return (a, b) => (a[key] > b[key] ? 1 : (a[key] < b[key] ? -1 : 0));
}
//id를 기준으로 오름차순 정렬
todos.sort(compare('id'));
console.log(todos);
/*
[
{ id: 1, content: 'HTML' },
{ id: 2, content: 'CSS' },
{ id: 4, content: 'JavaScript' }
]
*/
//content를 기준으로 오름차순 정렬
todos.sort(compare('content'));
console.log(todos);
/*
[
{ id: 2, content: 'CSS' },
{ id: 1, content: 'HTML' },
{ id: 4, content: 'JavaScript' }
]
*/
forEach
- for 문을 대체할 수 있는 고차 함수
- 자신의 내부에서 반복문을 실행
- 반복문을 추상화한 고차 함수로서 내부에서 반복문을 통해 자신을 호출한 배열을 순회하면서 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출
const numbers = [1, 2, 3];
const pows = [];
//numbers 배열의 모든 요소를 순회하며 콜백 함수를 반복 호출
numbers.forEach(item => pows.push(item ** 2));
console.log(pows); // [1, 4, 9]
- forEach 메서드의 콜백 함수는 forEach 메서드를 호출한 배열의 요소값과 인덱스, forEach 메서드를 호출한 배열 자체, 즉 this를 순차적으로 전달받을 수 있다
-> 콜백 함수를 호출할 때 3개의 인수, 요소값과 인덱스, this를 순차적으로 전달
[1, 2, 3].forEach((item ,index, arr) => {
console.log(`요소값:${item}, 인덱스:${index}, this:${JSON.stringify(arr)}`);
});
/*
요소값: 1, 인덱스: 0, this: [1, 2, 3]
요소값: 2, 인덱스: 1, this: [1, 2, 3]
요소값: 3, 인덱스: 2, this: [1, 2, 3]
*/
- forEach 메서드는 원본 배열을 변경하지 않는다
- 하지만 콜백 함수를 통해 원본 배열을 변경할 수는 있다
const numbers = [1, 2, 3];
//arr은 numbers를 가리킴
numbers.forEach((item, index, arr) => { arr[index] = item ** 2; });
console.log(numbers); // [1, 4, 9]
- forEach 메서드의 반환값은 언제나 undefined
const result = [1, 2, 3].forEach(console.log);
console.log(result); // undefined
- forEach 메서드의 두 번째 인수로 forEach 메서드의 콜백 함수 내부에서 this로 사용할 객체를 전달할 수 있다
class Numbers{
numberArray = [];
multiply(arr) {
arr.forEach(function (item) {
// TypeError : Cannot read property 'numberArray' of undefined
this.numberArray.push(item * item);
});
}
}
const numbers = new Numbers();
numbers.multiply([1, 2, 3]);
- 희소 배열의 경우 존재하지 않는 요소는 순회 대상에서 제외
//희소 배열
const arr = [1, , 3];
for(let i = 0; i < arr.length; i++) {
console.log(arr[i]); // 1, undefined, 3
}
arr.forEach(v => console.log(v)); // 1, 3
- forEach 메서드는 for문에 비해 성능지 좋지는 않지만 가독성은 더 좋다
- 요소가 대단히 많은 배열을 순회하거나 시간이 많이 걸리는 복잡한 코드 또는 높은 성능이 필요한 경우가 아니라면 for문 대신 forEach 메서드를 권장
some
- 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출
- 콜백 함수의 반환값이 단 한 번이라도 참이면 true, 모두 거짓이면 false를 반환
- 빈 배열인 경우 언제나 false를 반환
// 배열의 요소 중 10보다 큰 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item > 10); // true
// 배열의 요소 중 0보다 작은 요소가 1개 이상 존재하는지 확인
[5, 10, 15].some(item => item < 0); // false
// 배열의 요소 중 'banana'가 1개 이상 존재하는지 확인
['apple', 'banana', 'mango'].some(item => item === 'banana'); // true
// 빈 배열은 언제나 false
[].some(item => item > 3); // false
every
- 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출
- 콜백 함수의 반환값이 모두 참이면 true, 단 한 번이라도 거짓이면 false
- 빈 배열인 경우 언제나 true를 반환
// 배열의 모든 요소가 3보다 큰지 확인
[5, 10, 15].every(item => item > 3); // true
// 배열의 모든 요소가 10보다 큰지 확인
[5, 10, 15].every(item => item > 10); // false
// 빈 배열은 언제나 true
[].every(item => item > 3); // true
find
- 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출
- 반환값이 true인 첫 번째 요소를 반환
- 반환값이 true인 요소가 존재하지 않는다면 undefined를 반환
const users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' },
{ id: 3, name: 'Choi' },
{ id: 4, name: 'Park' }
];
// id가 2인 첫 번째 요소를 반환
// find 메서드는 배열이 아니라 요소를 반환
users.find(user => user.id === 2); // { id: 2, name: 'Kim' }
- filter 메서드는 콜백 함수의 호출 결과가 true인 요소만 추출한 새로운 배열을 반환
-> 반환값은 언제나 배열 - find 메서드는 콜백 함수의 반환값이 true인 첫 번째 요소를 반환
-> 결과값은 해당 요소값
[1, 2, 2, 3].filter(item => item === 2); // [2, 2]
[1, 2, 2, 3].find(item => item === 2); // 2
findIndex
- 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출
- 반환값이 true인 첫 번째 요소의 인덱스를 반환
- 반환값이 true인 요소가 존재하지 않는다면 -1 반환
const users = [
{ id: 1, name: 'Lee' },
{ id: 2, name: 'Kim' },
{ id: 2, name: 'Choi' },
{ id: 3, name: 'Park' }
];
// id가 2인 요소의 인덱스
users.findIndex(user => user.id === 2); // 1
// name이 'Park'인 요소의 인덱스
users.findIndex(user => user.id === 'Park'); // 3
// 위와 같이 프로퍼티 키와 프로퍼티 값으로 요소의 인덱스를 구하는 경우 다음과 같이 콜백 함수를 추상화할 수 있다
function predicate(key, value) {
// key와 value를 기억하는 클로저를 반환
return item => item[key] === value;
}
// id가 2인 요소의 인덱스
users.findIndex(predicate('id', 2)); // 1
// name이 'Park'인 요소의 인덱스
users.findIndex(predicate('name', 'Park')); // 3
flatMap
- map 메서드를 통해 생성된 새로운 배열을 평탄화
- map 메서드와 flat 메서드를 순차적으로 실행하는 효과
const arr = ['hello', 'world'];
// map과 flat을 순차적으로 실행
arr.map(x => x.split('')).flat();
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
arr.flatMap(x => x.split(''));
// ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']
- 단, flatMap 메서드는 flat 메서드처럼 인수를 전달하여 평탄화 깊이를 지정할 수는 없고 1단계만 평탄화
- map 메서드를 통해 생성된 중첩 배열의 평탄화 깊이를 지정해야 하면 flatMap 메서드를 사용하지 말고 map 메서드와 flat 메서드를 각각 호출
const arr = ['hello', 'world'];
// flatMap은 1단계만 평탄화
arr.flatMap((str, index) => [index, [str, str.length]]);
// [[0, ['hello', 5]], [1, ['world', 5]]] => [0, ['hello', 5], 1, ['world', 5]]
// 평탄화 깊이를 지정해야 하면 flatMap 메서드를 사용하지 말고 map 메서드와 flat 메서드를 각각 호출
arr.map((str, index) => [index, [str, str.length]]).flat(2);
// [[0, ['hello', 5]], [1, ['world', 5]]] => [0, 'hello', 5, 1, 'world', 5]
참조 Deep Dive