[JavaScript] 고차함수 & 배열 고차 함수

Letmegooutside·2022년 1월 26일
0

JavaScript

목록 보기
17/25
post-thumbnail

고차 함수(Higher order function)

함수를 인자로 전달받거나 함수를 결과로 반환하는 함수

고차 함수는 인자로 받은 함수를 필요한 시점에 호출하거나 클로저를 생성하여 반환한다.
(자바스크립트의 함수는 일급 객체이므로 값처럼 인자로 전달할 수 있으며 반환할 수도 있다.)

고차함수는 값뿐만 아니라 동작(action)을 추상화할 수 있게 한다.

예를 들어, 다음과 같이 새로운 함수를 생성하는 함수를 만들 수 있다.

function greaterThan(n) {
    return m => m > n;
}
let greaterThan10 = greaterThan(10);
console.log(greaterThan10(11)); // True

위 코드는 greaterThan 함수에 비교할 수만 넘겨주면 크기를 비교해주는 함수를 반환해준다.

내부에서 어떻게 크기 비교를 하는지는 사용하는 사람의 입장에서는 알 필요가 없다.
그냥 “10보다 큰 수인지 판별해주는 함수가 필요해. 그러니까 greaterThan에 10을 넘겨주면 10보다 큰 수인지 알 수 있는 함수를 줄거야” 정도만 판단하면 된다. 이것이 추상화다.
추상화를 사용하면 코드를 읽기 쉬워지고 내 코드에 버그가 생길 확률을 줄일 수 있다.

// 1
let total = 0, count = 1;
while (count <= 10) {
    total += count;
    count += 1;
}

//2
sum(range(1, 10));

1번과 2번은 똑같은 동작을 한다. 하지만, 딱 봐도 두 번째 코드의 의도를 알기 쉽다.

그리고 두 번째 코드로 작성하면 내가 작성한 코드가 버그를 가지고 있을 확률이 적다.
왜냐하면 ‘해결하려 하는 문제’와 ‘언어적 표현’이 일치하기 때문이다.
어떤 범위의 숫자를 더하는 것은 rangesum에 관한 내용이지, loopscounter에 관한 내용이 아니다.

고차 함수는 외부 상태 변경이나 가변(mutable) 데이터를 피하고 불변성(Immutability)을 지향하는 함수형 프로그래밍에 기반을 두고 있다.
함수형 프로그래밍은 순수 함수(Pure function)와 보조 함수의 조합을 통해 로직 내에 존재하는 조건문과 반복문을 제거하여 복잡성을 해결하고 변수의 사용을 억제하여 상태 변경을 피하려는 프로그래밍 패러다임이다.
조건문이나 반복문은 로직의 흐름을 이해하기 어렵게 하여 가독성을 해치고, 변수의 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있기 때문이다.

함수형 프로그래밍은 결국 순수 함수를 통해 부수 효과(Side effect)를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이려는 노력의 한 방법이라고 할 수 있다.

자바스크립트는 고차 함수를 다수 지원하고 있다. 특히 Array 객체는 매우 유용한 고차 함수를 제공한다.

배열의 고차함수

  • ✏️ 메소드는 this(원본 배열)를 변경한다.
  • 🔒 메소드는 this(원본 배열)를 변경하지 않는다.

Array.prototype.sort() ✏️ ES1

arr.sort([compareFunction])

배열의 요소를 적절한 위치에 정렬한 후 그 배열을 반환한다.

매개변수

compareFunction Optional

정렬 순서를 정의하는 함수로 생략하면 배열은 각 요소의 문자열 변환에 따라 각 문자의 유니 코드 코드 포인트 값에 따라 정렬된다.
compareFunction이 제공되면 배열 요소는 compare 함수의 반환 값에 따라 정렬된다.

  • compareFunction(a, b) < 0 : a가 앞으로 온다.
  • compareFunction(a, b) === 0 : 변경 없음
  • compareFunction(a, b) > 0 : b가 앞으로 온다.

반환 값

정렬한 배열. 복사본이 만들어지는 것이 아니라 원 배열이 정렬되는 것이다.

설명

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 ]

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

// 비교 함수 생략한 경우 
// 각 요소는 일시적으로 문자열로 변환되어 유니 코드 코드 포인트 값에 따라 정렬한다.
points.sort();
console.log(points); // [ 1, 10, 100, 2, 25, 40, 5 ]

기본 정렬 순서는 문자열 Unicode 코드 포인트 순서에 따르기 때문에 배열의 요소가 숫자 타입이라 할지라도 배열의 요소를 일시적으로 문자열로 변환한 후 정렬한다.
따라서 비교 함수를 생략하면 배열의 각 요소는 일시적으로 문자열로 변환되어 Unicode 코드 포인트 순서에 따라 정렬된다.

Array.prototype.forEach() 🔒 ES5

arr.forEach(callback(currentvalue[, index[, array]])[, thisArg])

주어진 함수를 배열 요소 각각에 대해 실행한다.

매개변수

callback : 각 요소에 대해 실행할 함수로 다음 세 가지 매개변수를 받는다.

  • currentValue : 처리할 현재 요소.
  • index Optional : 처리할 현재 요소의 인덱스.
  • array Optional : forEach()를 호출한 배열. (this)

thisArg Optional : callback을 실행할 때 this로 사용할 값.

반환 값

undefined

설명

  • 배열을 순회하며 배열의 각 요소에 대하여 인자로 주어진 콜백함수를 실행한다.

  • forEach 메소드는 원본 배열(this)을 변경하지 않는다. 하지만 콜백 함수는 원본 배열(this)을 변경할 수는 있다.

  • forEach 메소드는 for 문과는 달리 break 문을 사용할 수 없다. 즉, 배열의 모든 요소를 순회하며 중간에 순회를 중단할 수 없다.

  • forEach 메소드는 for 문에 비해 성능이 좋지는 않다. 하지만 for 문보다 가독성이 좋으므로 적극 사용을 권장한다.

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

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

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

// forEach 메소드는 원본 배열(this)을 변경하지 않는다.
// 하지만 콜백 함수는 원본 배열(this)을 변경할 수는 있다.
// 원본 배열을 직접 변경하려면 콜백 함수의 3번째 인자(this)를 사용한다.
numbers.forEach((item, index, self) => {
  self[index] = Math.pow(item, 2);
});

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

Array.prototype.map() 🔒 ES5

arr.map(callback(currentValue[, index[, array]])[, thisArg])

배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환한다.

매개변수

callback : 새로운 배열 요소를 생성하는 함수로 다음 세 가지 매개변수를 받는다.

  • currentValue : 처리할 현재 요소.
  • index Optional : 처리할 현재 요소의 인덱스.
  • array Optional : map()을 호출한 배열. (this)

thisArg Optional : callback을 실행할 때 this로 사용할 값.

반환 값

배열의 각 요소에 대해 실행한 callback의 결과를 모은 새로운 배열.

설명

  • 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백 함수의 반환값(결과값)으로 새로운 배열을 생성하여 반환한다. 이 때 원본 배열은 변경되지 않는다.

  • forEach 메소드는 배열을 순회하며 요소 값을 참조하여 무언가를 하기 위한 함수이며 map 메소드는 배열을 순회하며 요소 값을 다른 값으로 맵핑하기 위한 함수이다.

  • IE 9 이상에서 정상 동작한다.

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() 🔒 ES5

arr.filter(callback(element[, index[, array]])[, thisArg])

주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환한다.

매개변수

callback : 각 요소를 시험할 함수로 true를 반환하면 요소를 유지하고, false를 반환하면 버린다. 다음 세 가지 매개변수를 받습니다.

  • element : 처리할 현재 요소
  • index Optional : 처리할 현재 요소의 인덱스.
  • array Optional : filter()를 호출한 배열. (this)

thisArg Optional : callback을 실행할 때 this로 사용할 값.

반환 값

테스트를 통과한 요소로 이루어진 새로운 배열.
어떤 요소도 테스트를 통과하지 못했으면 빈 배열을 반환한다.

설명

  • filter 메소드를 사용하면 if 문을 대체할 수 있다.

  • 배열을 순회하며 각 요소에 대하여 인자로 주어진 콜백함수의 실행 결과가 true인 배열 요소의 값만을 추출한 새로운 배열을 반환한다.

  • 배열에서 특정 케이스만 필터링 조건으로 추출하여 새로운 배열을 만들고 싶을 때 사용한다. 이때 원본 배열은 변경되지 않는다.

  • IE 9 이상에서 정상 동작한다.

// 홀수만을 필터링한다 (1은 true로 평가된다)
const result = [1, 2, 3, 4, 5].filter((item, index, self) => item % 2);
console.log(result); // [ 1, 3, 5 ]

Array.prototype.reduce() 🔒 ES5

arr.reduce(callback[, initialValue])

배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환한다.

리듀서 함수는 네 개의 인자를 가진다.

  • 누산기 (acc)
  • 현재 값 (cur)
  • 현재 인덱스 (idx)
  • 원본 배열 (src)

리듀서 함수의 반환 값은 누산기에 할당되고, 누산기는 순회 중 유지되므로 결국 최종 결과는 하나의 값이 된다.

매개변수

callback : 배열의 각 요소에 대해 실행할 함수로 다음 네 가지 인수를 받는다.

  • accumulator : 콜백의 반환값을 누적한다. 콜백의 이전 반환값이나 콜백의 첫 번째 호출이면서 initialValue를 제공한 경우에는 initialValue의 값이다.
  • currentValue : 처리할 현재 요소.
  • currentIndex Optional : 처리할 현재 요소의 인덱스. initialValue를 제공한 경우 0, 아니면 1부터 시작한다. (0번째 요소는 초기값이 된다.)
  • array Optional : reduce()를 호출한 배열.

initialValue Optional : callback의 최초 호출에서 첫 번째 인수에 제공하는 값으로 초기값을 제공하지 않으면 배열의 첫 번째 요소를 사용한다.
빈 배열에서 초기값 없이 reduce()를 호출하면 오류가 발생한다. 따라서 reduce()를 호출할 때는 언제나 초기값을 전달하는 것이 보다 안전하다.

반환 값

누적 계산의 결과 값.

설명

const arr = [1, 2, 3, 4, 5];
// 초기 값으로 5를 전달한다.
const sum = arr.reduce((pre, cur) => pre + cur , 5);
console.log(sum); // 20
// 5 + 1 => 6 + 2 => 8 + 3 => 11 + 4 => 15 + 5

// 만약 초기값을 전달하지 않으면 0번째 요소인 1이 초기 값이 되고, 1번째 요소부터 순회를 시작한다.
const sum = arr.reduce((pre, cur) => pre + cur);
console.log(sum); // 15
// 1 + 2 => 3 + 3 => 6 + 4 => 10 + 5

Array.prototype.some() 🔒 ES5

arr.some(callback[, thisArg])

배열 안의 어떤 요소라도 주어진 판별 함수를 통과하는지 테스트한다.
(빈 배열에서 호출하면 무조건 false를 반환한다.)

매개변수

callback :각 요소를 시험할 함수로 다음 세 가지 인수를 받는다.

  • currentValue : 처리할 현재 요소.
  • index Optional : 처리할 현재 요소의 인덱스.
  • array Optional : some()을 호출한 배열. (this)

thisArg Optional : callback을 실행할 때 this로 사용할 값.

반환 값

callback이 하나의 배열 요소라도 참인 결과를 반환할 경우 true, 그 외엔 false

설명

  • some은 callback이 참을 반환하는 요소를 찾을 때까지 배열에 있는 각 요소에 대해 한 번씩 callback 함수를 실행하고, 해당하는 요소를 발견한 경우 즉시 true를 반환한다.
// 배열 내 요소 중 10보다 큰 값이 1개 이상 존재하는지 확인
let res = [2, 5, 8, 1, 4].some(item => item > 10);
console.log(res); // false

res = [12, 5, 8, 1, 4].some(item => item > 10);
console.log(res); // true

Array.prototype.every() 🔒 ES5

every(callbackFn, thisArg)

배열 안의 모든 요소가 주어진 판별 함수를 통과하는지 테스트한다.

매개변수

callbackFn : 각 요소를 시험할 함수로 다음 세 가지 인수를 받는다.

  • element : 배열에서 처리되는 현재 요소
  • index Optional : 처리할 현재 요소의 인덱스.
  • array Optional : every()을 호출한 배열. (this)

thisArg Optional : callbackFn을 실행할 때 this로 사용하는 값.

반환 값

callbackFn이 모든 배열 요소에 대해 참인 값을 반환하는 경우 true, 그 외엔 false

설명

  • callback이 거짓을 반환하는 요소를 찾을 때까지 배열에 있는 각 요소에 대해 한 번씩 callbackFn 함수를 실행하고, 해당하는 요소를 발견한 경우 즉시 false를 반환한다.

  • 빈 배열에서 호출하면 무조건 true를 반환한다.

// 배열 내 모든 요소가 10보다 큰 값인지 확인
let res = [21, 15, 89, 1, 44].every((item) => item > 10);
console.log(res); // false

res = [21, 15, 89, 100, 44].every((item) => item > 10);
console.log(res); // true

Array.prototype.find() 🔒 ES6

arr.find(callback[, thisArg])

주어진 판별 함수를 만족하는 첫 번째 요소의 값을 반환한다. 그런 요소가 없다면 undefined를 반환한다.

매개변수

callback : 배열의 각 값에 대해 실행할 함수로 아래의 세 인자를 받는다.

  • element : 콜백함수에서 처리할 현재 요소.
  • index Optional : 콜백함수에서 처리할 현재 요소의 인덱스.
  • array Optional : find()를 호출한 배열. (this)

thisArg Optional : callback을 실행할 때 this로 사용하는 값.

반환 값

주어진 판별 함수를 만족하는 첫 번째 요소의 값. 그 외에는 undefined

설명

  • callback 함수가 참을 반환 할 때까지 해당 배열의 각 요소에 대해서 callback 함수를 실행하고 만약 어느 요소를 찾았다면 해당 요소의 값을 즉시 반환한다.

  • callback은 0 부터 length - 1 까지 배열의 모든 인덱스에 대해 호출되며, 값이 지정되지 않은 요소도 포함하여 모든 인덱스에 대해 호출된다.
    따라서, 희소 배열 (sparse arrays)의 경우에는 값이 지정된 요소만 탐색하는 다른 메소드에 비해 더 비효율적입니다.

const users = [
  { id: 1, name: 'Lee' },
  { id: 2, name: 'Kim' },
  { id: 2, name: 'Choi' },
  { id: 3, name: 'Park' }
];

// 콜백함수를 실행하여 그 결과가 참인 첫번째 요소를 반환한다.
let result = users.find(item => item.id === 2);

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

Array.prototype.findIndex() 🔒 ES6

arr.findIndex(callback(element[, index[, array]])[, thisArg])

주어진 판별 함수를 만족하는 배열의 첫 번째 요소에 대한 인덱스를 반환한다. 만족하는 요소가 없으면 -1을 반환한다.

매개변수

callback : 배열의 각 값에 대해 실행할 함수로 아래의 세 인자를 받는다.

  • element : 콜백함수에서 처리할 현재 요소.
  • index Optional : 콜백함수에서 처리할 현재 요소의 인덱스.
  • array Optional : findIndex()를 호출한 배열. (this)

thisArg Optional : callback을 실행할 때 this로 사용하는 값

반환 값

요소가 테스트를 통과하면 배열의 인덱스. 그 외에는 -1

설명

  • 콜백 함수가 참을 반환 할 때까지 배열의 모든 배열 인덱스(0..length-1)에 대해 한 번씩 콜백 함수를 실행하고 이러한 요소가 발견되면 해당 요소의 인덱스를 즉시 반환한다.

  • ES6에서 새롭게 도입된 메소드로 IE에서는 지원하지 않는다.

const users = [
  { id: 1, name: 'Lee' },
  { id: 2, name: 'Kim' },
  { id: 2, name: 'Choi' },
  { id: 3, name: 'Park' }
];

// 콜백함수를 실행하여 그 결과가 참인 첫번째 요소의 인덱스를 반환한다.
function predicate(key, value) {
  return function (item) {
    return item[key] === value;
  };
}

// 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



Reference
https://woong-jae.com/javascript/210829-higher-order-function
https://poiemaweb.com/js-array-higher-order-function
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array

0개의 댓글