유명한 담벼락

27장 : 배열 - 9) 배열 고차함수

by 담담이담

9. 배열 고차 함수

 

개요 

 

고차함수는 함수를 파라미터로 전달받거나 반환하는 함수를 말한다.

 

고차함수는 함수형 프로그래밍에 기반을 두고 있다.

 

함수형 프로그래밍은 외부 상태의 변경이나 가변 데이터를 피한다.

또한, 조건문과 반복문을 제거하여 복잡성을 해결하고

변수 사용을 억제하여 상태변경을 피하려는 프로그래밍 패러다임이다.

 

1) Array.prototype.sort

 

sort 메서드는 원본 배열을 직접 변경하여 정렬한 배열을 반환한다.

원본 배열이 변경되는 걸 원하지 않는다면 slice()를 한 다음 sort()를 하면 된다.

기본적으로 "오름차순"으로 배열을 정렬한다. 

한글도 영어도 둘 다 오름차순으로 정렬된다. 

오름차순이 아닌 내림차순으로 정렬하려면 sort 후 reverse 메소드를 사용하여 순서를 뒤집는다.

 

 

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']
const fruits = ['바나나', '오렌지', '사과'];

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

// sort 메서드는 원본 배열을 직접 변경한다.
console.log(fruits); // ['바나나', '사과', '오렌지']

문자열로 이뤄진 배열의 정렬은 아무 문제가 없지만, 

숫자로 이뤄진 배열의 정렬은 주의가 필요하다. 

 

왜냐면, sort 메소드는 요소를 문자열로 인식하고 정렬하기에

(정확히는 유니코드 포인트로 변환 후에 정렬)

 

숫자 요소를 정렬할 때는 sort 메서드에

정렬 순서를 정의하는 비교함수를 인수로 전달해야한다.

 

비교함수는

양수 or 음수  or 0을 반환해야하며,

비교함수의 반환값이 0보다 작으면 첫 인수를 우선으로 정렬하고,

0이면 정렬하지 않으며,

0보다 크면 두 번째 인수를 우선해서 정렬한다.

 

 

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

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

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

 

객체를 요소로 갖는 배열의 정렬은 다음과 같다.

const todos = [
  { id: 4, content: 'JavaScript' },
  { id: 1, content: 'HTML' },
  { id: 2, content: 'CSS' }
];

// 비교 함수. 매개변수 key는 프로퍼티 키다.
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, map, filter, reduce, some, every, find, findIndex 등의 공통점

: 1) 모든 요소를 순회하면서 인수로 전달받은 콜백함수를 반복 호출한다.

2) 콜백함수의 인수로 이를 호출한 배열의 요소, 인덱스, 호출한 배열 그 자체를 전달한다.

 

 

 forEach, map, filter의 차이점

 

forEach - 언제나 undefined를 반환

(반복문을 대체하기 위한 함수)

 

map -  콜백함수의 반환값으로 구성된 새 배열 반환

(요소 값을 다른 값으로 매핑한 새 배열을 생성하기 위한 함수)

 

filter - 콜백함수의 반환값이 true인 요소만 추출한 새 배열 반환

 

 

2) Array.prototype.forEach

 

조건문이나 반복문은 흐름을 이해하기 어렵게 하며, 

특히 for문은 반복을 위한 변수를 선언해야하므로

함수형 프로그래밍이 추구하는 바와 맞지 않는다.

 

forEach 메서드는 for문을 대체할 수 있는 고차함수이다.

이는 자신의 내부에서 반복문을 실행한다. 

 

즉, forEach는

반복문을 추상화한 고차함수로 

내부에서 반복문을 통해 

자신을 호출한 배열을 순회하면서

수행해야할 처리를 콜백함수로 전달받아 반복호출한다.

 

const numbers = [1, 2, 3];
let pows = [];

// forEach 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
numbers.forEach(item => pows.push(item ** 2));
console.log(pows); // [1, 4, 9]

 

위 예제에서 forEach는 numbers의 모든 요소를 순회하면서 콜백함수를 반복호출한다.

numbers의 요소가 3개이므로, 콜백함수도 3번 호출된다.

 

// forEach 메서드는 콜백 함수를 호출하면서 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 메서드는 원본 배열(this)을 변경하진 않지만, 

콜백함수를 통해 원본 배열을 변경할 수는 있다. 

 

const numbers = [1, 2, 3];

// forEach 메서드는 원본 배열을 변경하지 않지만 콜백 함수를 통해 원본 배열을 변경할 수는 있다.
// 콜백 함수의 세 번째 매개변수 arr은 원본 배열 numbers를 가리킨다.
// 따라서 콜백 함수의 세 번째 매개변수 arr을 직접 변경하면 원본 배열 numbers가 변경된다.
numbers.forEach((item, index, arr) => { arr[index] = item ** 2; });
console.log(numbers); // [1, 4, 9]

forEach의 반환값은 언제나 undefined이다. 

 

forEach는 for문과 달리 

break, continue를 사용할 수 없다.

따라서 배열의 모든 요소를 빠짐없이 순회해야한다.

 

3) Array.prototype.map

const numbers = [1, 4, 9];

// map 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
// 그리고 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다.
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 ]

map도 자신이 호출한 배열의 모든 요소를 순회하면서

인수로 전달 받은 콜백함수를 반복 호출한다.

 

그리고 콜백함수의 반환값들로 구성된 새 배열을 반환한다.

이 때 원본 배열은 변경되지 않는다.

 

map 메서드가 생성하여 반환하는 새 배열의 length 프로퍼티 값은 

map 메소드를 호출한 배열의 length 값과 일치한다.

따라서 map 메서드를 호출한 배열과 map 메서드가 생성해서 반환한 배열은 1:1 매핑된다.

 

4) Array.prototype.filter

filter 메서드는 자신이 호출한 배열의 

모든 요소를 순회하면서 인수로 전달받은 콜백함수를 반복 호출한다.

 

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

// filter 메서드는 numbers 배열의 모든 요소를 순회하면서 콜백 함수를 반복 호출한다.
// 그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다.
// 다음의 경우 numbers 배열에서 홀수인 요소만을 필터링한다(1은 true로 평가된다).
const odds = numbers.filter(item => item % 2);
console.log(odds); // [1, 3, 5]

그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새 배열을 반환한다.

이때 원본 배열은 변경되지 않는다.

 

따라서 호출한 배열에서 필터링 조건을 만족하는 요소만 추출해서 

새 배열을 만들고 싶을 때 사용한다. 

 

따라서 filter 메서드가 생성하여 반환한 새 배열의 length는

filter를 호출한 배열의 length 값보다 같거나 작다.

자신이 호출한 배열에서 특정 요소를 제거하기 위해서도 사용한다.

 

class Users {
  constructor() {
    this.users = [
      { id: 1, name: 'Lee' },
      { id: 2, name: 'Kim' }
    ];
  }

  // 요소 추출
  findById(id) {
    // id가 일치하는 사용자만 반환한다.
    return this.users.filter(user => user.id === id);
  }

  // 요소 제거
  remove(id) {
    // 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' }]

// id가 1인 사용자를 제거한다.
users.remove(1);

user = users.findById(1);
console.log(user); // []

'모던 자바스크립트 딥다이브' 카테고리의 다른 글

32장 : String - 작성 중  (1) 2023.10.25
12장 : 함수  (0) 2023.10.23
5장 : 표현식과 문  (1) 2023.10.23
23장 : 실행 컨텍스트(2)  (0) 2023.10.16
23장 : 실행 컨텍스트(1)  (0) 2023.10.15

블로그의 정보

유명한 담벼락

담담이담

활동하기