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