일급객체와 고차함수

피자냠냠(피자냠냠)·2023년 1월 12일

일급객체


일급 객체의 특별 취급 3가지

JavaScript에 특별한 대우를 받는 일급 객체(first-class citizen)가 있다. 대표적인 일급 객체 중 하나가 함수 이다. JavaScript에서 함수는 아래와 같이 특별하게 취급됩니다.

  1. 변수에 할당(assignment) 할 수 있다.

  1. 다른 함수의 전달인자(argument)로 전달될 수 있다.

    매개변수가 함수가 될 수 있다.

    이때 전달되는 함수는 콜백함수(callback function)라고한다.

  1. 다른 함수의 결과로서 리턴될 수 있다. (커링함수, 클로저)

    함수를 불렀더니 함수가 나온다 (클로저랑 똑같잖아?)

    이를 커링함수라고한다.

이 말은 함수를 데이터(string, number, boolean, array, object) 다루듯 다룰 수 있다는 걸 의미한다.

고차 함수


고차 함수는 다른 함수를 인자로 받거나 다른 함수를 리턴하는 함수를 말한다. 이 때 다른 함수(caller)의 인자로 전달되는 함수를 콜백 함수라고 한다.

함수를 리턴하는 함수를 일컫는 용어가 따로 있는데 이런 함수를 커링 함수라고 한다.

  1. 다른 함수를 인자로 받는 경우

    function double(num) {   return num * 2;
    }
    
    function doubleNum(func, num) { // 고차함수 
      return func(num);
    }
    
    let output = doubleNum(double, 4);// double은 콜백함수가 된다.
    
    console.log(output); // -> 8
  2. 함수를 리턴하는 경우

    function adder(added) { // 고차함수
      return function (num) {
        return num + added;
      };
    }
    
    // adder(5)는 함수이므로 함수 호출 연산자 '()'를 사용할 수 있습니다.
    let output = adder(5)(3); // -> 8
    console.log(output); // -> 8
    
    // adder가 리턴하는 함수를 변수에 저장할 수 있다. (클로저)
    // javascript에서 함수는 일급 객체이기 때문이다.
    const add3 = adder(3);
    output = add3(2);
    console.log(output); // -> 5

1+2 함수를 인자로 받고, 함수를 리턴하는 경우

function double(num) {
  return num * 2;
}

function doubleAdder(added, func) { // 고차함수
  const doubled = func(added);
  return function (num) {
    return num + doubled;
  };
}

// doubleAdder(5, double)는 함수이므로 함수 호출 기호 '()'를 사용할 수 있따.
doubleAdder(5, double)(3); // -> 13
// double은 콜백함수가 된다.

// doubleAdder가 리턴하는 함수를 변수에 저장할 수 있다. (일급 객체)
const addTwice3 = doubleAdder(3, double);
addTwice3(2); // --> 8

내장 고차 함수 이해하기


JavaScript에는 기본적으로 내장된 고차 함수가 여럿 있다.

그 중 대표적으로 사용하는 배열 메서드는 filter, map, reduce 등이 있다.

세 가지 메서드의 공통점은 배열의 각 요소함수를 적용시키는 것이다.

함수를 전달인자로 받기 때문에 고차함수 이다.

filter

filter 메서드는, 모든 배열 요소 중에서 특정 조건에 부합하는 요소를 걸려내는 메서드이다.

여기서 걸러내는 기준이 되는 특정 조건filter 메서드의 전달인자로 전달됩니다. 이때 전달되는 조건은 함수의 형태입니다.

filter를 사용하면 배열의 요소를 하나씩 적용하고 true면 요소를 반환하고 false이면 삭제해버린다.

filter 메서드는 배열의 요소를 콜백 함수에 다시 전달합니다. 콜백 함수는 전달받은 배열의 요소를 받아 함수를 실행하고, 콜백 함수 내부의 조건에 따라 참(true) 또는 거짓(false)을 리턴해야 합니다. 처음 본 코드에 이 점을 반영하여 다시 코드를 작성하면, 다음과 같습니다.

// 짝수 나오게하는 함수
const isEven = function (num) {
  return num % 2 === 0;
};

let arr = [1, 2, 3, 4];
// let output = arr.filter(짝수);
// '짝수'를 판별하는 함수가 조건으로서 filter 메서드의 전달인자로 전달됩니다.
let output = arr.filter(isEven);
console.log(output); // ->> [2, 4]

const isLteFive = function (str) {
  // Lte = less then equal
  return str.length <= 5;
};

arr = ['hello', 'code', 'states', 'happy', 'hacking'];
// output = arr.filter(길이 5 이하)
// '길이 5 이하'를 판별하는 함수가 조건으로서 filter 메서드의 전달인자로 전달됩니다.
let output = arr.filter(isLteFive);
console.log(output); // ->> ['hello', 'code', 'happy']

문제

// 문제
// 만화책 식객 27권의 정보가 배열에 담겨있습니다. 
// 출판 연도가 2003년인 단행본만 담은 배열을 만드세요.

const cartoons = [
  {
    id: 1,
    bookType: 'cartoon',
    title: '식객',
    subtitle: '어머니의 쌀',
    createdAt: '2003-09-09',
    genre: '요리',
    artist: '허영만',
    averageScore: 9.66,
  },
  {
    id: 2,
    // .. 이하 생략
  },
  // ... 이하 생략
];

// 단행본 한 권의 출판 연도가 2003인지 확인하는 함수
const isCreatedAt2003 = function (cartoon) {
  const fullYear = new Date(cartoon.createdAt).getFullYear()
  return fullYear === 2003;
};

// 출판 연도가 2003년인 책의 모음
const filteredCartoons = cartoons.filter(isCreatedAt2003);

map

배열을 가져와 각 요소에 함수를 적용하고 반환한다.

기존의 배열은 유지된다.

문제

// 만화책 식객 27권의 정보가 배열에 담겨있습니다. 각 책의 부제(subtitle)만 담은 배열을 만드세요.

const cartoons = [
  {
    id: 1,
    bookType: 'cartoon',
    title: '식객',
    subtitle: '어머니의 쌀',
    createdAt: '2003-09-09',
    genre: '요리',
    artist: '허영만',
    averageScore: 9.66,
  },
  {
    id: 2,
    // .. 이하 생략
  },
  // ... 이하 생략
];

// 만화책 한 권의 부제를 리턴하는 로직(함수)
const findSubtitle = function (cartoon) {
  return cartoon.subtitle;
};

// 각 책의 부제 모음
const subtitles = cartoons.map(findSubtitle); // ['어머니의 쌀', ...]

reduce

arr.reduce()
arr.reduce(함수, 초기값) // 초기값 안쓰면 첫번째 요소가 초기값이 된다.
함수부분 : (누적 계산값(acc), 현재값(cur)) => {return 계산값}
현재값은 배열을 순차적으로 넣어준다

문제

만화책 식객 27권의 정보가 배열에 담겨있습니다. 각 단행본의 평점의 평균을 리턴하세요.

실제 코드

reduce는 이렇게 여러 데이터를, 하나의 데이터로 응축(reduce)할 때 사용합니다.

// 단행본 모음
const cartoons = [
  {
    id: 1,
    bookType: 'cartoon',
    title: '식객',
    subtitle: '어머니의 쌀',
    createdAt: '2003-09-09',
    genre: '요리',
    artist: '허영만',
    averageScore: 9.66,
  },
  {
    id: 2,
    // .. 이하 생략
  },
  // ... 이하 생략
];

// 단행본 한 권의 평점을 누적값에 더한다.
const scoreReducer = function (sum, cartoon) {
  return sum + cartoon.averageScore;
};

// 초기값에 0을 주고, 숫자의 형태로 평점을 누적한다.
let initialValue = 0
// 모든 책의 평점을 누적한 평균을 구한다.
const cartoonsAvgScore = cartoons.reduce(scoreReducer, initialValue) / cartoons.length;

[코드] 배열의 reduce 메서드로 모든 책의 누적 평균을 구할 수 있습니다.

위 영상에서 소개한 것처럼, reduce는 배열 요소의 합을 구할 때만 사용하는 것이 아닙니다. 이어서 소개하는 사용법을 참고하여, reduce를 더욱 풍부하게 사용할 수 있습니다.

reduce의 색다른 사용법

배열을 문자열로

수도 코드

  • 배열의 각 요소 : 유저 정보
  • 응축하는 방법 (함수) : 하나의 유저의 이름과 쉼표를 이어 붙입니다(concat)
  • 원하는 형태 : 문자열로 누적합니다.
  • 응축된 결과 : 쉼표로 구분되는 모든 유저의 이름
function joinName(resultStr, user) {
  resultStr = resultStr + user.name + ', ';
  return resultStr;
}

let users = [
  { name: 'Tim', age: 40 },
  { name: 'Satya', age: 30 },
  { name: 'Sundar', age: 50 }
];

users.reduce(joinName, '');

[코드] 콜백 함수 joinName은 users 배열 안에 있는 요소의 이름을 하나로 응축합니다.

실습

reduce의 콜백 함수 joinName은 총 몇 번 실행되나요? 다음 표에 들어가는 값은 각각 무엇인가요?

호출 횟수. | resultStr.| user     | 리턴 값
-------- | --------- | ---------| ---------
1번째 호출 |  `______` | `______` | `______`
2번째 호출 |  `______` | `______` | `______`
n번째 호출 |  `______` | `______` | `______`

최종 리턴 값: ______

실습

reduce의 콜백 함수 joinName은 총 몇 번 실행되나요? 다음 표에 들어가는 값은 각각 무엇인가요?

호출 횟수  | resultStr. \| user| 리턴 값
-------- | --------- | --------- | ---------
1번째 호출 |  `______` | `______` | `______`
2번째 호출 |  `______` | `______` | `______`
n번째 호출 |  `______` | `______` | `______`

최종 리턴 값: ______

배열을 객체로

수도 코드

  • 배열의 각 요소 : 유저 정보
  • 응축하는 방법 (함수) : 유저 한 명의 이름 중 첫 글자를 주소록 객체 속성의 키(key)로, 유저의 정보를 주소록 객체 속성의 값(value)으로 추가합니다.
  • 원하는 형태 : 주소록 객체에 누적합니다.
  • 응축된 결과 : 모든 유저의 정보가 알파벳으로 구분된 주소록
function makeAddressBook(addressBook, user) {
  let firstLetter = user.name[0];

  if(firstLetter in addressBook) {
    addressBook[firstLetter].push(user);
  } else {
    addressBook[firstLetter] = [];
    addressBook[firstLetter].push(user);
  }

  return addressBook;
}

let users = [
  { name: 'Tim', age: 40 },
  { name: 'Satya', age: 30 },
  { name: 'Sundar', age: 50 }
];

users.reduce(makeAddressBook, {});

[코드] 콜백 함수 makeAddressBook은 users 배열 안에 있는 요소로 주소록을 만듭니다.

실습

reduce의 콜백 함수 makeAddressBook은 총 몇 번 실행되나요? 다음 표에 들어가는 값은 각각 무엇인가요?

호출 횟수| addressBook | user     | 리턴 값
-------- | --------- | ---------| ---------
1번째 호출 |  `______` | `______` | `______`
2번째 호출 |  `______` | `______` | `______`
n번째 호출 |  `______` | `______` | `______`

최종 리턴 값

{
  T: [
    { name: 'Tim', age: 40 }
  ],
  S: [
    { name: 'Satya', age: 30 },
    { name: 'Sundar', age: 50 }
  ]
}

자주 쓰는 메소드

find

findIndex

some

every

sort

나중에 배우자.

profile
교사에서 개발자로

0개의 댓글