[C/F TIL] 21일차 - JavaScript 고차함수, 내장 고차함수, 중요성

mu-eng·2023년 5월 10일
1

TIL (in boost camp)

목록 보기
22/53
post-thumbnail

Code States
Front-end boost camp
Today
I
Learned

🥹 을마나 어려울지.. 모두가 겁주는.. 그래서 걱정이 되는... 21일차 수업 시작~~~!


🥹 일급 객체

  • 비행기에도 퍼스트 클래스가 있듯, JavaScript에도 특별한 대우를 받는 일급 객체가 있다!
  • 함수 : 대표적인 일급 객체
    -- 변수에 할당할 수 있다.
    -- 다른 함수의 전달인자로 전달될 수 있다.
    -- 다름 함수의 결과로써 리턴될 수 있다.
    -- 즉, 함수를 변수에 할당할 수 있기 때문에, 함수를 배열의 요소나 객체의 속성 값으로 저장할 수 있고, 함수를 데이터처럼 다룰 수 있다!

🥹 고차함수

  • 함수를 전달인자로 받을 수 있고, 함수를 리턴할 수 있는 함수
    -- 다른 함수를 인자로 받는 경우 ▼
function double(num) {
  return num * 2;
}

function doubleNum(func, num) {
  return func(num);
}

/*
 * 함수 doubleNum은 다른 함수를 인자로 받는 고차 함수입니다.
 * 함수 doubleNum의 첫 번째 인자 func에 함수가 들어올 경우
 * 함수 func는 함수 doubleNum의 콜백 함수입니다.
 * 아래와 같은 경우, 함수 double은 함수 doubleNum의 콜백 함수입니다.
 */
let output = doubleNum(double, 4);
console.log(output); // -> 8

-- 함수를 리턴하는 경우 ▼

function adder(added) {
  return function (num) {
    return num + added;
  };
}

/*
 * 함수 adder는 다른 함수를 리턴하는 고차 함수입니다.
 * adder는 인자 한 개를 입력받아서 함수(익명 함수)를 리턴합니다.
 * 리턴되는 익명 함수는 인자 한 개를 받아서 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

-- 함수를 인자로 받고, 함수를 리턴하는 경우 ▼

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

function doubleAdder(added, func) {
  const doubled = func(added);
  return function (num) {
    return num + doubled;
  };
}

/*
 * 함수 doubleAdder는 고차 함수입니다.
 * 함수 doubleAdder의 인자 func는 함수 doubleAdder의 콜백 함수입니다.
 * 함수 double은 함수 doubleAdder의 콜백으로 전달되었습니다.
 */

// doubleAdder(5, double)는 함수이므로 함수 호출 기호 '()'를 사용할 수 있습니다.
doubleAdder(5, double)(3); // -> 13

// doubleAdder가 리턴하는 함수를 변수에 저장할 수 있습니다. (일급 객체)
const addTwice3 = doubleAdder(3, double);
addTwice3(2); // --> 8
  • 콜백 함수 : 다른 함수의 전달인자로 전달되는 함수 (< 고차함수)
  • 커링 함수 : 함수를 리턴하는 함수 (< 고차함수)

🥹 내장 고차함수

  • JavaScript에 기본적으로 내장된 고차 함수는 여러개
  • 배열 메서드들 중 일부가 대표적인 고차 함수

✔️ filter 메서드

  • 모든 배열의 요소 중에서 특정 조건을 만족하는 요소를 걸러내는 메서드
    -- ex) number 타입을 요소로 갖는 배열에서 짝수만 걸러냄
    -- ex) string 타입을 요소로 갖는 배열에서 길이가 10 이하인 문자열만 걸러냄
  • 과정 반드시 기억하기
    -- 1) 배열의 각 요소가
    -- 2) 특정 논리(함수)에 따르면, 사실(true)일 때
    -- 3) 따로 분류한다.(filter)
    예시 ▼
// 문제 : 만화책 식객 27권의 정보가 배열에 담겨있다. 출판 연도가 2003년인 단행본만 담은 배열을 만드시오
// 수도코드 : 과정대로 -> 1) 각 식객 1-27권의 정보 -> 2) 책의 출판 연도가 2003년 -> 3) 따로 분류
// 단행본 모음
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);

  • filter 느낌~ 알기 ▼
// 아래 코드에서 '짝수'와 '길이 5 이하'는 문법 오류(syntax error)에 해당합니다.
// 의미만 이해해도 충분합니다.
let arr = [1, 2, 3, 4];
let output = arr.filter(짝수);
console.log(output); // ->> [2, 4]

arr = ['hello', 'code', 'states', 'happy', 'hacking'];
output = arr.filter(길이 5 이하)
console.log(output); // ->> ['hello', 'code', 'happy']
// 아래 코드는 정확한 표현 방식은 아닙니다.
// 의미만 이해해도 충분합니다.

let arr = [1, 2, 3];
// 배열의 filter 메서드는 함수를 전달인자로 받는 고차 함수입니다.
// arr.filter를 실행하면 내부적으로 arr에 접근할 수 있다고 생각해도 됩니다.
arr.filter = function (arr, func) {
  const newArr = [];
  for (let i = 0; i < arr.length; i++) {
    // filter에 전달인자로 전달된 콜백 함수는 arr의 각 요소를 전달받아 호출됩니다.
    // 콜백 함수가 true를 리턴하는 경우에만 새로운 배열에 추가됩니다.
    if (func(arr[i]) === true) {
      newArr.push(this[i]);
    }
  }
  // 콜백 함수의 결과가 true인 요소들만 저장된 배열을 리턴합니다.
  return newArr;
};
  • filter 메서드 사용 예시 ▼
// 함수 표현식
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']

✔️ map 메서드

  • 모든 요소에게 동일한 행동을 준 값에 대하여 반환
  • 과정 반드시 기억하기
    -- 1) 배열의 각 요소가
    -- 2) 특정 논리(함수)에 따르면, 사실(true)일 때
    -- 3) 따로 분류한다.(filter)
    예시 ▼
// 만화책 식객 27권의 정보가 배열에 담김. 각 책의 부제(subtitle)만 담은 배열을 만드세요.
// 수도 코드 : 과정대로! -> 1) 각 식객 1-27권의 정보 -> 2) 책 한 권의 부제를 찾습니다. -> 3) 각 식객 1-27권의 부제

// 만화책 모음
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 메서드

  • 배열을 하나의 값으로 만들어 주는 메서드
  • 여러 데이터를 하나의 데이터로 응축(reduce)할 때 사용
  • 초기값을 정할 수 있음
    -- 정하지 않을 경우 배열의 첫번째 요소 값이 초기값
  • 첫번째 요소 값이 초기값일 경우,
    -- 그 다음 요소가 현재값이 됨
// 예시
const arr = [1, 2, 3];
const result = arr.reduce((acc, cur, idx) => {
  let newAcc = acc + cur;
  return newAcc;
}, 1)
result;
  • 다음 과정을 반드시 기억하기
    -- 1) 배열의 각 요소를
    -- 2) 특정 방법(함수)에 따라
    -- 3) 원하는 하나의 형태로
    -- 4) 응축합니다. (reduction)
    예시 ▼
// 문제 : 만화책 식객 27권의 정보가 배열에 담김, 각 단행본의 평점의 평균을 리턴
// 수도코드 : 과정대로 -> 1) 각 식객 1-27권의 정보 -> 2) 각 단행본의 평점을 누적값에 더함 -> 3) 숫자로 누적 -> 4) 각 단행본의 평점의 합을 단행본의 길이로 나눈 평점의 평균
// 단행본 모음
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 응용
    -- 배열을 문자열로 ▼
// 수도코드 : 과정대로 -> 1) 유저 정보를 -> 2) 하나의 유저의 이름과 쉼표를 이어 붙인다. -> 3) 문자열로 누적한다. -> 4) 쉼표로 구분되는 모든 유저의 이름을 결과로
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, '');

-- 배열을 객체로 ▼

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, {});

// 최종 리턴 값
{
  T: [
    { name: 'Tim', age: 40 }
  ],
  S: [
    { name: 'Satya', age: 30 },
    { name: 'Sundar', age: 50 }
  ]
}

🥹 고차함수의 중요성

  • 추상화란? : 복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것
    -- ex)'-1'이라는 문자를 보고 "-1은 0보다 1만큼 작은 수다."라고 설명하는 것
    -- ex) javascript를 포함한 많은 프로그래밍 언어 역시 추상화의 결과, CPU는 0과 1만 이해하지만 우리는 javascript의 도움을 받아 10을 출력한다.

  • 추상화 = 생산성(productivity)의 향상

  • 함수 = 값을 전달받아 값을 리턴 = 값에 대한 복잡한 로직은 감추어져 있음 = 값 수준에서의 추상화
    -- 값 수준에서의 추상화 : 단순히 값(value)을 전달받아 처리하는 수준
    -- 사고의 추상화 : 함수(사고의 묶음)를 전달받아 처리하는 수준

  • 고차함수를 통해 보다 높은 수준에서 생각할 수 있다
    -- 고차 함수 = 함수를 전달받거나 함수를 리턴 = 사고(함수)에 대한 복잡한 로직은 감추어져 있다 = 사고 수준에서의 추상화

=> 즉, 추상화의 수준이 높아지면 생산성도 비약적으로 상승할 수 있다!

🥹 사고 수준의 추상화의 예시

  • 예시 데이터 ▼
const data = [
  {
    gender: 'male',
    age: 24,
  },
  {
    gender: 'male',
    age: 25,
  },
  {
    gender: 'female',
    age: 27,
  },
  {
    gender: 'female',
    age: 22,
  },
  {
    gender: 'male',
    age: 29,
  },
];
  • 모든 작업을 하나의 함수로 작성하는 코드 ▼
// 남성들의 평균 나이를 구하는 하나의 함수
function getAverageAgeOfMaleAtOnce(data) {
  const onlyMales = data.filter(function (d) {
    // data.filter는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
    // 그 결과가 true인 요소만을 갖는 배열을 리턴합니다.
    return d.gender === 'male';
  });

  const numOfMales = onlyMales.length;

  const onlyMaleAges = onlyMales.map(function (d) {
    // onlyMales.map는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
    // 각 결과를 요소로 갖는 배열을 리턴합니다.
    return d.age;
  });

  const sumOfAges = onlyMaleAges.reduce(function (acc, cur) {
    // onlyMaleAges.reduce는 배열의 각 요소에 인자로 전달받은 함수를 적용하고,
    // 각 결과를 두 번째 인자로 전달받은 초기값(0)에 누적한 결과를 리턴합니다.
    return acc + cur;
  }, 0);

  return sumOfAges / numOfMales;
}
  • 입력된 함수를 순차적으로 실행하는 고차 함수 compose ▼
function getOnlyMales(data) {
  return data.filter(function (d) {
    return d.gender === 'male';
  });
}

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

function getAverage(data) {
  const sum = data.reduce(function (acc, cur) {
    return acc + cur;
  }, 0);
  return sum / data.length;
}

function compose(...funcArgs) {
  // compose는 여러 개의 함수를 인자로 전달받아 함수를 리턴하는 고차 함수입니다.
  // compose가 리턴하는 함수(익명 함수)는 임의의 타입의 data를 입력받아,
  return function (data) {
    // funcArgs의 요소인 함수들을 차례대로 적용(apply)시킨 결과를 리턴합니다.
    let result = data;
    for (let i = 0; i < funcArgs.length; i++) {
      result = funcArgs[i](result);
    }
    return result;
  };
}

// compose를 통해 함수들이 순서대로 적용된다는 것이 직관적으로 드러납니다.
// 각각의 함수는 다른 목적을 위해 재사용(reuse) 될 수 있습니다.
const getAverageAgeOfMale = compose(
  getOnlyMales, // 배열을 입력받아 배열을 리턴하는 함수
  getOnlyAges, // 배열을 입력받아 배열을 리턴하는 함수
  getAverage // 배열을 입력받아 `number` 타입을 리턴하는 함수
);

const result = getAverageAgeOfMale(data);
console.log(result); // --> 26

🥹 고차함수 문제풀기

총 32개의 문제 순차적으로 풀고 리뷰하며 업로드 예정

🥹 21일차 수업을 마치며...

개념 자체는 얼추 이해가 되었으나 응용이 어려운 상태다. 이는 조건문 반복문 풀때 처럼 많이 풀어보는 방법이 맞을 것 같아서, 오늘 페어님과 함께 뚝딱이며 풀어본 문제들과 아직 풀지 못한 문제들을 풀 예정이다.

어렵지만 해야지 뭐
해야지!

profile
[무엥일기] 무엥,,, 내가 머쨍이 개발자가 될 수 이쓰까,,,

0개의 댓글