TIL - 콜백함수, 고차함수

Naakite·2022년 2월 27일

📁JavaScript

목록 보기
7/7
post-thumbnail
콜백함수에 대해 조금 더 자세히 정리하기 위해 찾다보니 콜백함수와 고차함수가 연관된 것이라는 것을 알게 되었고, 또 고차함수를 읽다보니 배열 고차함수라는 것을 알게되었다! 그래서 콜백함수, 고차함수, 배열고차함수에 대해 정리를 해보았다.

📗 모던 자바스크립트 Deep Dive 내용을 바탕으로 작성하였습니다.

🏁 콜백함수와 고차함수의 관계
매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수
매개변수를 통해 콜백 함수를 전달받는 함수를 고차함수

🎯 콜백함수와 고차함수

function repeat(n) {
  for( let i = 0; i<n; i++) console.log(n);
}

repeat(5); // 0 1 2 3 4 5

콜백함수는 드림코딩-동기비동기 에서 간단히 배웠었는데, 다시한번 정리해보자.

위의 코드가 주로 우리가 알고있는 함수다. repeat함수는 매개변수를 통해, 전달받은 숫자만큼 반복하여 console.log(n) 을 호출한다.

만약 매개변수를 통해 전달받은 숫자만큼 반복하여 숫자를 출력하고, 또 전달받은 숫자범위 내에서 홀수일때만 n을 출력하고 싶다면,어떻게 작성해야할까?

function repeat1(n) {
  for( let i = 0; i<n; i++) console.log(n);
}

function repeat2(n) {
  for( let i=0; i<n; i++) {
     if(i%2) console.log(i);
  }
}

repeat1(5); // 0 1 2 3 4 
repeat2(5); // 1 3 

이 코드를 콜백함수를 이용해서 간단히 만들어보자. 그리고 고차함수에 대해서도 알아보자!
먼저 다시 콜백함수의 개념을 생각해보면, 함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수 이다. 그럼 위의 코드중에서, 공통적으로 수행할 기능의 함수를 정의해두고, 경우에 따라 변경되는 기능을 추상화해서 함수 외부에서 함수 내부로 전달하면된다. 그리고 매개변수를 통해 콜백 함수를 전달받는 함수는 고차함수 가 된다.

  // repeat 함수는 고차함수다. (다른 함수를 인수로 받기때문에!)
function repeat(num,func) { 
  for(let i=0; i<n; i++){
     func(i); // func 는 repeat 함수의 콜백함수다.
  }
}
const logAll = function (i) {
  console.log(i);
};
const logOdds = function(i) {
   if(i%2) console.log(i)
};
// 반복 호출할 함수를 전달한다.
// logAll과 logOdds 은 repeat 의 콜백함수가 된다. 
repeat(5, logAll);  // 0 1 2 3 4 
repeat(5, logOdds); // 1 3

🎯 배열 고차 함수

드림코딩 자바스크립트 배열 강의를 듣고 강의에서 배운 내용들을 작성한 적이 있다. 겹치는 내용들이 많지만, 이전에는 대략적인 강의 내용을 정리한 것이라면, 오늘은 조금 더 자세하게 정리할 예정이기에, 다시한번 더 작성하고자 한다. [드림 코딩 배열 강의 정리📄]

매개변수를 통해 콜백 함수를 전달받는 함수를 고차함수라고 하는데,
배열 고차 함수는 함수를 인자로 전달받거나 함수를 결과로 반환하는 함수를 말한다.

📌 Array.prototype.sort

sort 메서드는 원본 배열을 직접 변경하고 정렬된 배열(기본적으로 오름차순)을 반환한다.

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 메서드는 배열의 요소들을 일시적으로 문자열로 변환한 후에 유니코드 코드 포인트의 순서를 기준으로 정렬한다.

그렇기때문에, 숫자요소를 정렬할때는 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] ); // 1 100

📌 Array.prototype.forEach

내가 기억하는 forEach 메서드는 for문을 대체한다는 것, 그리고 콜백함수를 호출한다는 것이다. 자신의 내부에서 반복문을 실행하는 것인데, 책에 적혀있는 정의를 인용하면 반복문을 추상화한 고차 함수로서 내부에서 반복문을 통해 자신을 호출한 배열을 순회하면서 수행해야 할 처리를 콜백 함수로 전달받아 반복 호출 하는 고차함수라고 할 수 있다.

아래 예제에서는 배열의 요소가 5개이므로 콜백 함수도 5번 호출된다!
forEach 메서드는 원본 배열자체를 변경하는 것이 아니다! 콜백 함수를 통해서 원본 배열을 변경한다!
forEach 메서드는 콜백함수를 호출할 때 세 가지의 인수를 전달받는다.

currentValue : 처리할 현재 요소
index : 처리할 현재 요소의 인데스
thisArg : forEach()를 호출한 배열

// forEach 메서드는 콜백 함수를 호출하면서 요소값, 인덱스, this 인수를 전달.
[1,2,3,4,5].forEach((item, index, arr)=> {
       console.log(`요소값: ${item}, 인덱스: ${index}, this:${JSON.stringify(arr)}`);
}); 
/*
"요소값: 1, 인덱스: 0, this:[1,2,3,4,5]"
"요소값: 2, 인덱스: 1, this:[1,2,3,4,5]"
"요소값: 3, 인덱스: 2, this:[1,2,3,4,5]"
"요소값: 4, 인덱스: 3, this:[1,2,3,4,5]"
"요소값: 5, 인덱스: 4, this:[1,2,3,4,5]"
*/

📍 forEach 메서드는 원본 배열을 변경하지 않지만, 콜백 함수를 통해 원본 배열을 변경하고 반환값은 언제나 undefined 이다.

const numbers = [1,2,3];

const num_Array = numbers.forEach((item, index, arr) => {
   arr[index] = item ** 2;
});

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

forEach 메서드를 호출한 코드를 함수에 할당하면 undefined가 반환된 것을 알 수있다.

📍 forEach 메서드는 중간에 멈출수 없다!
fo문과 달리 break, continue 문을 사용할 수 없고, 배열의 모든 요소를 빠짐없이 모두 순회해야만 끝이 난다!


📌 Array.prototype.map

map 메서드는 forEach 메서드와 똑같이 자신을 호출한 배열의 모든 요소를 순회하면서 인수로 전달받은 콜백 함수를 반복 호출한다. (원본 배열 변경 X) 이때 forEach 문과 다른점은 콜백 함수의 반환값들로 구성된 새로운 배열을 반환한다는 것이다!

const numbers = [1,2,3];

const num_Array = numbers.map(item => item*2);

console.log(numbers);  // [1,2,3]
console.log(num_Array); //[2,4,6]

map 메서드는 콜백함수를 호출할 때 세 가지의 인수를 전달받는다.

currentValue : 처리할 현재 요소
index : 처리할 현재 요소의 인데스
thisArg : map()를 호출한 배열


📌 Array.prototype.filter

filter 메서드는 자신을 호출한 배열의 모든 요소를 순회하면서, 인수로 전달받은 콜백 함수를 반복 호출한다. 그리고 콜백 함수의 반환값이 true인 요소로만 구성된 새로운 배열을 반환한다. (원본 배열 변경 X)

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

const odds = numbers.filter(item => item %2);
console.log(odds); // [1,3,5]

filter 메서드는 콜백함수를 호출할 때 세 가지의 인수를 전달받는다.

currentValue : 처리할 현재 요소
index : 처리할 현재 요소의 인데스
thisArg : map()를 호출한 배열

📍 filter 메서드는 자신을 호출한 배열에서 특정 요소를 제거하기 위해 사용할 수도 있다!


📌 Array.prototype.reduce

reduce 메서드는 자신이 호출한 배열을 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출한다. 그리고 콜백 함수의 반환값을 다음 순회 시에 콜백 함수의 첫번째 인수로 전달하면서 콜백 함수를 호출하여 하나의 결과값을 만들어 반환한다. 쉽게말해 배열의 값들을 하나의 새로운 값으로 합치는 것이다! (원본 배열 변경 X) 이때 초깃값을 설정할 수 있는데, 만약 초깃값을 설정하지 않으면, 배열의 첫번째 요소가 초깃값이 된다!

reduce 메서드는 첫번째 인수로 콜백함수, 두번째 인수로 초깃값을 전달받는다.
그리고 콜백함수를 호출할 때 초깃값 or 콜백함수의 이전 반환값 =(누산기), 현재값, 현재 인덱스, reduce()를 호출한 배열 네 가지의 인수를 전달받는다.

const sum = [1,2,3,4,5].reduce((accumulator,currentValue) => {
     return accumulator + currentValue;
}, 0); // 초깃값: 0

console.log(sum); //15

위 코드에 대한 reduce 메서드의 작동방식이다.
총 5번의 콜백함수가 호출되고, 값이 더해지고, 그 다음 콜백함수가 호출되면 그 이전의 반환값과 현재 값을 더하는 반복되는 작동과정을 볼수 있다. 이러한 반복을 통해서 reduce 메서드는 하나의 결과값을 반환한다.

reduce 메서드를 이용해서 다양하게 활용할 수 있는데, 이 부분은 따로 정리할 예정이다..🧐


📌 Array.prototype.some & every

some 메서드와 every 메서드는 둘 다 값이 조건에 해당하는지 판단할때 사용하기 좋은 메서드들인 것 같다! 하지만 이 둘의 반환값에 차이가 있는데, 이때 some 메서드는 콜백 함수의 반환값이 단 한번이라도 참이면 true 를, 모두 거짓이면 false를 반환한다. 반대로 every 메서드는 콜백 함수의 반환값이 모두 참이면 true를, 단 한번이라도 거짓이면 false 를 반환한다.

📍 배열에서 모든 값이 조건에 해당하는지 판단 -> (만약 하나의 값이라도 조건에 해당하지 않는다면 false 출력)-> every 메서드
📍 배열에서 하나의 값이라도 조건에 해당하는지 판단 -> (만약 하나의 값이라도 조건에 해당한다면 true 출력) -> some 메서드

🔎 의문점 .. forEach 메서드와 every,some 메서드의 차이점은?

🧐 여기서 의문점이 하나 생겼다.. 그럼 forEach 메서드와 some & every 차이는 무엇일까..forEach 메서드로도 충분이 배열의 요소가 조건에 해당하는지 판단할 수 있는데..반환값으로 boolean 타입이 필요할때 some, every 를 사용하는 것인가..?

✅ some, every 메서드와 forEach 메서드의 가장 큰 차이점은 break 기능 유무이다! forEach 메서드의 특징 중 하나가 중간에 멈출수 없다는 것이었다.

예를 들어 배열의 값이 10보다 작을 경우에만 출력하도록 했다. 그럼 1이 출력되고,
20은 10보다 크기때문에 출력되지않고, every 메서드는 배열 요소값이 한개라도 조건에 해당하지 않으면 false 를 반환하기때문에, false 가 반환되고 반복문을 멈춘 것을 볼 수있다.

그럼 forEach 메서드를 똑같이 실행해보자.

forEach 메서드는 중간에 멈추지 않기때문에 배열의 요소를 순회하면서 10보다 작은 값들만 출력한 것을 볼 수 있다.

만약 반복문을 배열 끝까지 실행할 필요가 없다면! 조건에 맞게 every 메서드나 some 메서드를! 아니면 forEach 메서드를 이용하자!


📌 Array.prototype.find

ES6에서 도입된 find 메서드는 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하여, 반환값이 true인 첫번째 요소를 반환한다. 콜백 함수의 반환값이 true인 요소가 존재하지 않는다면 undefined을 반환한다

🔎 filter 메서드와 find 메서드의 차이점은 무엇일까?
filter 메서드는 배열을 반환하고, find 메서드는 요소를 반환한다.

[1,2,2,3].filter(item => item ===2); // [2,2]
[1,2,2,3].find(item => item === 2); // 2 

📌 Array.prototype.findIndex

ES6에서 도입된 findIndex 메서드는 자신을 호출한 배열의 요소를 순회하면서 인수로 전달된 콜백 함수를 호출하여 반환값이 true 인 첫번째 요소의 인덱스를 반환한다. 만약 콜백함수의 반환값이 true인 요소가 없다면 -1을 반환한다.

const num = [5, 12, 8, 130, 44];
const isLargeNumber = (element) => element > 13;
console.log(array1.findIndex(isLargeNumber));
// expected output: 3
profile
👩‍💻🏃‍♀️

0개의 댓글