JavaScript - 고차함수

Kim-DaHam·2023년 3월 14일
0

JavaScript

목록 보기
8/18
post-thumbnail

🔥 학습목표

  • 일급객체에 대해 설명할 수 있다.
  • 고차함수에 대해 설명할 수 있다.
  • 배열 내장 고차함수 filter, map, reduce를 활용하여 프로그램을 작성할 수 있다.
  • [S1U5] JS 함수, [S1U8] JS 배열, 자바스크립트 Deep Dive 18장을 복습하며 일급 객체 및 고차함수에 대해 더 깊게 공부한다.



🟩 고차 함수

🟣 일급 객체(first-class-citizen)

다음 세 가지 조건을 가지면 일급 객체다.

  • 변수에 할당(assignment)할 수 있다.
  • 다른 함수의 전달인자(argument)로 전달될 수 있다.
  • 다른 함수의 결과로서 리턴될 수 있다.

즉 객체를 일반 데이터(string, number, boolean, array, object)처럼 다룰 수 있다는 것이다.

자바스크립트에서 함수는 일급 객체다.

함수가 일급 객체라는 것은, 함수를 객체와 동일하게 사용할 수 있다는 뜻이다. 객체는 값이므로 함수는 값과 동일하게 취급할 수 있다.



🟣 고차 함수란?

함수를 전달인자(argument)로 받을 수 있고, 함수를 리턴할 수 있는 함수

⬜ 콜백 함수(callback function)

다른 함수(caller)의 전달인자로 전달되는 함수를 콜백 함수라고 한다.
주로 어떤 작업이 완료된 후 호출하는 경우가 많아 '콜백' 이라는 이름이 붙여졌다.

콜백 함수를 전달받은 고차함수(caller)는 함수 내부에서 콜백 함수를 호출할 수 있다.

혹은 조건에 따라 콜백 함수의 실행 여부를 결정할 수도 있다.

또는 아예 호출하지 않을 수도 있다.

아예 호출하지 않을 수도 있다.

여러 번 실행할 수도 있다.

(특정 작업이 끝난 뒤 호출하는 경우는 이후에 학습한다.)

커링 함수 / 고차 함수 뭐가 다를까?

  • 커링 함수: '함수를 전달인자로 받는 함수' 라는 뜻에 한정.
  • 정확하게 구분하자면, 고차 함수가 커링 함수를 포함한다.



🟩 내장 고차 함수

🟣 배열 내장 고차 함수

⬜ filter(item, index, arr)

모든 배열의 요소에서 특정 조건을 만족하는 요소를 걸러낸다.

└▷ 원본 배열은 변경되지 않는다.

let arr = [100, 200, 300, 400, 500];

const isOrderThan300 = function(num){
	return num > 300;
}

let output = arr.filter(isOrderThan300);
console.log(output); // [400, 500]
  • filter 메서드는 배열의 요소를 콜백 함수에 전달한다.
  • 콜백 함수는 배열의 요소를 받아 함수를 실행한다.
  • 콜백 함수 내부 조건에 따라 참/거짓을 리턴한다.
  • 결과가 참인 배열의 요소만 걸러내 새로운 배열을 반환한다.

=> 정리

배열의 각 요소가
특정 논리(함수)에 따라 true일 때
따로 분류한다(filter).


⬜ map(item, index, arr)

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

└▷ 원본 배열은 변경되지 않는다.

const albums = [
  {
  	id: 1,
    title: 'A Night Of The Opera',
    year: 1975,
    //... 이하 생략
  },
  {
    id: 2,
    title: 'Sheer Heart Attack',
  	//... 이하 생략
  },
  // ... 이하 생략
  ];

const findTitle = function(album){
	return album.title;
}

const titles = albums.map(findTitle); // ['A Night Of The Opera', 'Sheer Heart Attack', ...]

=> 정리
배열의 각 요소가
특정 논리(함수)에 의해
다른 요소로 바뀌어 지정(map)된다.


⬜ reduce(callback, initial)

배열의 모든 요소를 순회하며 인수로 전달 받은 콜백 함수를 반복 호출한 뒤, 하나의 결과값을 만들어 반환한다.

└▷ 원본 배열은 변경되지 않는다.

callback(초기값(이전 반환값), 요소값, 인덱스, 배열 자체)

const queen = [
  {
    name: 'Freddie Mercury',
    age: 45,
    //... 이하 생략
  },
  {
    name: 'Brian May',
    age: 76,
  },
  //...이하 생략
];

const ageReducer = function (sum, member){
	return sum + member.age;
}

const memberAvgAge = queen.reduce(ageReducer, 0) / queen.length;

=> 정리
배열의 각 요소를
특정 방법(함수)에 따라
원하는 하나의 형태로
응축한다.(reduction)



🟣 고차 함수(reduce)의 활용

⬜ 배열을 문자열로 바꾸기

function joinName(resultStr, member) {
	return resultStr = resultStr + member.name + ',';
}

let members = [
  {name: 'Freddie', age: 45},
  {name: 'Brian', age: 76},
  {name: 'Roger', age: 74},
  {name: 'John', age: 72}
];

members.reduce(joinName, '');

⬜ 배열을 객체로 바꾸기

function makeAdrressBook(addressBook, member){
	let firstLetter = member.name[0];
	if(firstLetter in addressBook)
      addressBook[firstLetter].push(member);
  	else {
      addressBook[firstLetter] = [];
      addressBook[firstLetter].push(member);
    }
    return addressBook;
}

let members = [
  {name: 'Freddie', age: 45},
  {name: 'Brian', age: 76},
  {name: 'Roger', age: 74},
  {name: 'John', age: 72}
];

members.reduce(makeAddressBook, {});

⬜ 최대값 구하기

const values = [100, 200, 300, 400, 500];

const max = values.reduce((acc, cur)=>(acc>cur? acc : cur), 0);

하지만 최대값을 구할 때는 Math.max 메서드를 쓰는 게 더 직관적이다.

const max = Math.max(...values);

⬜ 요소의 중복 횟수 구하기

const alphabets = ['a', 'a', 'b', 'c', 'd'];

const count = alphabets.reduce((acc, cur)=>{
  // 빈 객체에 요소값인 cur을 프로퍼티 키로, 요소의 개수를 값으로 할당한다.
	acc[cur] = (acc[cur] || 0) + 1;
  return acc;
}, {})
// {'a':1} => {'a':2} => {'a':2, 'b':1} => {'a':2, 'b':1, 'c':1} => ...

⬜ 중첩 배열 평탄화

const values = [1, [2,3], 4, [5,6]];
const flatten = values.reduce((acc,cur)=>acc.concat(cur), []);

하지만 중첩 배열을 평탄화 할 때는 Array.prototype.flat 메서드를 사용하는 게 더 직관적이다.


⬜ 중복 요소 제거

const alphabets = ['a', 'a', 'b', 'c', 'd'];

const result = alphabets.reduce(
  // (초기값, 요소값, 인덱스, 배열 자체)
  (unique, val, i, _values) =>
  // 현재 순회 중인 요소의 인덱스가 val의 인덱스와 같다면 val은 처음 순회하는 요소다.
  // 같지 않으면 val은 중복된 요소다.
  // 처음 순회하는 요소만 초기값 빈 배열에 담는다.
  _values.indexOf(val) === i ? [...unique, val] : unique,
  [])
);

⬜ 새롭게 추가한 요소의 인덱스 찾기

이건 코플릿 문제였는데 (수학 공식처럼) 아예 외우는 식으로 기억하고 있어도 나쁘지 않다고 하여 적어둔다.

function getIndex(arr, num) {
  // num을 추가하고 정렬한다는 가정 하에, num의 위치 찾기
  return arr.filter(function (el) {
    return el < num; // num의 위치를 찾는 거지만 부등호를 넣지 않는 이유는
  }).length; // length 를 반환할 것이기 때문이다. (+1 됨)
}



🟩 고차 함수의 중요성

고차 함수를 쓰는 이유가 뭘까? 에 대하여 설명한다.

🟣 고차 함수의 추상화

⬜ 추상화란?

복잡한 어떤 것을 압축해서 추출한 상태로 만드는 것

  • 추상화 = 생산성의 향상

프로그램을 작성할 때, 자주 반복해서 사용되는 로직은 별도의 함수로 작성한다. 이 역시 추상화의 좋은 예다.

함수를 통해 얻은 이러한 추상화를 한 단계 더 높인 것이 고차 함수다.

  • 함수 = 값을 전달 받아 리턴 = 값에 대한 복잡한 로직은 감춰져 있다 = 값 수준의 추상화

  • 값 수준의 추상화 = 단순히 값(value)을 전달받아 처리하는 수준

  • 사고의 추상화 = 함수(사고의 묶음)를 전달받아 처리하는 수준

  • 고차함수 = 함수를 전달 받거나 함수를 리턴 = 사고(함수)에 대한 복잡한 로직은 감춰져 있다 = 사고 수준에서의 추상화

=> 추상화의 수준이 높아지면 생산성도 상승한다.


⬜ 사고 수준 추상화의 예시

아래와 같은 데이터가 있다고 할 때,

const data = [
  {
    gender: 'male',
    age: 24,
  },
  {
    gender: 'male',
    age: 25,
  },
  {
    gender: 'female',
    age: 27,
  },
  {
    gender: 'female',
    age: 22,
  }
  ];

남성들의 평균 나이를 구하는 하나의 함수를 작성하면 아래와 같다.

function getAverageAgeOfMaleAtOnce(data) {
  const onlyMales = data.filter(function (d) {
    return d.gender === 'male';
  });

  const numOfMales = onlyMales.length;

  const onlyMaleAges = onlyMales.map(function (d) {
    return d.age;
  });

  const sumOfAges = onlyMaleAges.reduce(function (acc, cur) {
    return acc + cur;
  }, 0);

  return sumOfAges / numOfMales;
}

하지만 위 작업은 '남성'들의 평균 나이만 구할 수 있다.

저 함수를 조금 더 사용성 있게 바꾸려면 '성별'이라는 파라미터를 만드는 게 좋다.

그럼에도 저 함수 속에는 '평균 나이를 구하는' 데에만 쓰이긴 아까운 부분들이 있다.

map을 사용하여 age만 분리하거나, reduce를 사용하여 평균을 내는 함수는

'남성' 중 '최연소 나이'를 구하거나, '여성' 중 '최연소 나이와 최연장 나이의 차이' 등 충분히 다른 목적을 위해 재사용 될 수 있다.

따라서 아래와 같이 재사용 될만한 가치가 있는 것들의 단위로 함수를 분리할 수 있다.

function getOnlyFemales(data) {
  return data.filter(function(d){
    return d.gender === 'female';
  });
}

function getOnlyAges(data) {
  return data.map(function(d){
    return d.age;
  });
}

function getAverage(data){ // 생략 }
  
// pipe 함수
function compose(...funcArgs) {
  return function(data){
    let result = data;
    for(let i = 0; i<funcArgs.length; i++)
      result = funcArgs[i](result);
    return result;
  };
}
  
 const getAverageAgeOfFemale = compose(
   getOnlyFemales,
   getOnlyAges,
   getAverage
 };
   
 const result = getAverageAgeOfFemale(data);



profile
다 하자

0개의 댓글