[CS] 고차함수 Day-19

cptkuk91·2021년 10월 29일
0

CS

목록 보기
36/139

고차함수에서 우리가 배워야 될 것

  • 일급 객체(first-class citizen)의 세 가지 특징
  • 고차 함수(higher-order function)
  • 고차 함수를 JS로 작성

일급 객체 (first-class citizen)이란?

JavaScript에서 일급 객체는 특별한 대우를 받습니다. 대표적인 일급 객체가 함수입니다.

  • 변수에 할당 가능
  • 다른 함수의 인자로 전달 가능
  • 다른 함수의 결과로 리턴 가능

함수를 변수에 할당할 수 있기 때문에, 함수를 배열의 요소나 객체의 속성값으로 저장할 수 있습니다.

ex) 변수에 함수를 할당하는 경우 (함수 표현식)

const square = function(num) {
	return num * num;
};

result = square(7);
console.log(result); // 49 출력

위와 같은 함수 표현식 (function expression)은 함수 선언식 (function declaration)과 다르게 호이스팅 적용이 안됩니다.

  • 호이스팅 : 선언된 위치에 관계없이 어디서든 함수를 사용할 수 있도록 합니다.

따라서 함수 선언식의 호이스팅에 지나치게 의존한다면 코드의 유지 보수가 어려워질 수 있습니다. 호이스팅을 제외하면 함수 선언식과 함수 표현식은 크게 차이가 없습니다.

고차 함수 (higher order function)

고차 함수는 함수를 인자로 받을 수 있고, 함수의 형태로 리턴할 수 있는 함수입니다. 따라서 함수 내부에서 변수에 함수를 할당할 수 있습니다. 그리고 함수를 통해 변수를 반환할 수 있습니다.
(고차 함수에서는 함수를 인자로 전달하고, 고차 함수는 함수 자체를 반환합니다. 변수가 빠졌을 뿐 동일하게 동작합니다.)

다른 함수(caller)의 인자(argument)로 전달되는 함수를 콜백 함수(callback function)이라고 합니다. 특정 작업이 끝났을 때 호출하는 함수입니다.

콜백 함수

콜백 함수를 전달받은 고차 함수는, 함수 내부에서 콜백 함수를 호출(invoke)할 수 있습니다. 함수 (caller)는 조건에 따라 콜백 함수의 실행 여부를 결정할 수 있습니다. 호출하지 않을 수 있고, 여러 번 실행할 수도 있습니다.

ex) 함수를 인자로 받는 함수 (고차 함수)

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

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

let result = doubleNum(double, 4);
console.log(result) // 8;

ex) 함수를 리턴하는 경우

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

let result = adder(5)(3);
console.log(result); // 8 출력

const add3 = adder(3); // adder가 함수에 변수 3을 저장해놓고
let result2 = add3(2); // 저장된 변수 3을 added에 넣은 상태에서 function(num)에 2를 삽입한다.
console.log(result2); // 따라서 결과값은 5가 나온다.

ex) 함수를 인자로 받고, 함수를 리턴하는 경우

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

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

doubleAdder(5, double)(3); // 13 출력
// added에 5를 넣고, func에 double을 받아온다.
// doubled = double(5)가 된다.
// double return 값은 10이다.
// return function(3)을 통해
// return의 최종 결과값은 3 + 10 이다.

const addTwice3 = doubleAdder(3, double);
addTwice3(2); // 8 출력

JavaScript에 내장된 고차함수

배열 내장 고차함 수인 filter에 대해서 배워야 합니다.

filter에 기반한 나머지 고차함수

  • forEach
  • find
  • filter
  • map
  • reduce
  • sort
  • some
  • every

abstraction(추상화)에 대한 이해가 필요합니다.

내장 고차함수에 대한 이해

JavaScript에는 다양한 고차함수가 존재합니다. 그 중 배열 메소드 중 일부가 대표적인 고차함수에 해당합니다. 가장 기본적인 filter에 대한 이해 후 다음으로 넘어가겠습니다.

filter 메소드는 걸러내기 위한 조건을 명시한 함수라고 생각하면 됩니다.
ex)

let arr = [1,2,3,4];

arr.filter = function(arr, func){
	const newArr = [];
    for(let i = 0; i < arr.length; i++){
    	if(func(arr[i]) === true){
        	newArr.push(this[i]);
        }
    }
    return newArr;
};

ex) 실제 예시를 활용한 filter 1번

const isEven = function(num) {
	return num % 2 === 0;
};

let arr = [1,2,3,4];
let result = arr.filter(isEven);
console.log(result); // [2,4];

ex) 실제 예시를 활용한 filter 2번

const isLetterFive = function(str){
	return str.length <= 5;
};

arr = ["Hello", "Code", "States", "Happy", "Hacking"];
let result = arr.filter(isLetterFive);
console.log(result); // ["Hello", "Code", "Happy"] 출력

Map에 대해서 알아보자.

모든 요소에게 동일한 행동을 준 값에 대하여 모두 반환합니다.
행동은 개발자가 함수로 직접 작성해야 합니다. (기존 배열은 원본 유지가 됩니다.)
원하는 행동에 따라서 결과가 달라집니다.

ex) Map 예시

let arr = [1,2,3];

let result = arr.map(function(ele){
	return ele * 2
});

return result; // [2,4,6] 출력

ex) map 실제 코드

const movies = [
	{
    	id: 1,
        bookType: 'Movie',
        title: 'Mission Impossible',
        subtitle: 'Powerful',
        createdAt: '2010-10-10',
        genre: 'Action',
        artist: 'Cruz',
        AverageScore: 9.55,
    },
    {
    	id: 2,
        // .. 이하 생략
    },
    // ... 이하 생략
];

const findSubtitle = function(movies) {
	return movies.subtitle;
}

const subtitles = movies.map(findSubtitle);
// 각 영화들의 subtitle을 출력할 수 있다.

filter에 대해서 알아보자.

filter는 모든 요소 중 내가 원하는 값만 filtering 하여 반환합니다.
코드는 개발자가 직접 작성해야하고, 함수로 작성하여 인자로 받습니다.
기존 배열은 유지됩니다.
원하는 filtering에 따라서 결과가 달라집니다.

ex) filter 예시

let arr = [1,2,3];

let result = arr.filter(function(ele){
	return ele % 2 === 0
}); 

// 1,3 출력

ex) filter 실제 예시

const movies = [
	{
    	id: 1,
        Type: 'movies',
        title: 'Mission Impossible',
        subtitle: 'Festival',
        createdAt: '2010-10-10',
        genre: 'Action',
        artist: 'Cruz',
        averageScore: 9.55,
    }
    {
    	id: 2,
        // ... 이하 생략
    },
    // ... 이하 생략
];

const isCreatedAt2010 = function(movies){
	const fullYear = new Data(movies.createdAt).getFullYear();
    return fullYear === 2010;
};

const filteredMovies = movies.filter(isCreatedAt2010);
// 2010년에 만들어진 영화들 출력

reduce에 대해서 알아보자.

reduce는 배열을 하나의 값으로 만들어줍니다.
reduce는 초기값을 설정할 수 있습니다. (설정을 안할경우 배열의 첫 번째 값이 초기값이 됩니다.)
초기값은 누적값에 저장됩니다.
이후 배열의 끝 까지 누적값에 저장이 됩니다. (저장이 될 때는 개발자가 원하는 행동에 따라 값이 바뀝니다.)
마지막에는 누적값의 값을 반환하게 됩니다.

ex) reduce 초기값 설정이 없는 예시

let arr = [1,2,3];

let result = arr.reduce(function(acc, cur, idx) {
	acc += cur;
    return acc;
});

result; // 6

ex) reduce 초기값이 설정된 예시

let arr = [1,2,3];
let result = arr.reduce(function(acc, cur, idx){
	acc += cur;
    return acc;
}, 1); // 1로 초기값을 설정

result; // 결과는 7을 출력한다.

ex) reduce 실제 코드

const movies = [
	{
    	id: 1,
        Type: 'movies',
        title: 'Mission Impossible',
        subtitle: 'Festival',
        createdAt: '2010-10-10',
        genre: 'Action',
        artist: 'Cruz',
        averageScore: 9.55,
    }
    {
    	id: 2,
        // ... 이하 생략
    },
    // ... 이하 생략
];

const scoreReducer = function(sum, movies) {
	return sum + movies.averageScore;
};

let initialValue = 0;
const moviesAvgScore = movies.reduce(scoreReducer, initialValue) / movies.length;

reduce의 색다른 사용법

배열을 문자열로 출력할 수 있습니다.

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

let users = [
    { name: 'KJ', age: 40 },
    { name: 'SJ', age: 30 },
    { name: 'SH', age: 50 }
];

console.log(users.reduce(joinName, '')); // KJ, SJ, SH, 출력

배열을 객체로

function makeAddressBook(addressBook, user){
    let firstLetter = user.name[0];

    if(firstLetter in addressBook){
        addressBook[firstLetter].push(user); // firstLetter 가 있는 경우
    } else {
        addressBook[firstLetter] = []; // firstLetter 가 없는 경우
        addressBook[firstLetter].push(user);
    }

    return addressBook;
}

let users = [
    { name: 'KJ', age: 40},
    { name: 'SJ', age: 30},
    { name: 'UH', age: 50}
];

console.log(users.reduce(makeAddressBook, {}));
//{
//    K: [ { name: 'KJ', age: 40} ],
//    S: [ { name: 'SJ', age: 30} ],
//    U: [ { name: 'UH', age: 50} ],
//} 출력

고차 함수를 사용해야하는 이유

추상화(abstraction) 때문입니다. 복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것이 추상화입니다. 예를들면 우리가 1부터 5까지 더했을 때 어떤 과정을 거쳐서 15라는 값이 나오는지 몰라도 결과값을 알 수 있습니다.

ex)

function sum(num1, num2){
	return num1 + num2;
}
const result = sum(2, 5);
console.log(result); // 7 출력

이런 문제는 컴퓨터의 추상화를 통해 해결할 수 있습니다.

추상화 = 생산성 향상

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

고차 함수는 이 추상화의 수준을 사고의 추상화 수준으로 끌어올립니다.

  • 값 수준의 추상화 : 단순히 값(value)를 전달받아 처리하는 수준
  • 사고의 추상화 : 함수를 전달받아 처리하는 수준

추상화의 수준이 높아지는 만큼 생산성은 향상됩니다.

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

// 남자만 있는 배열을 만들어보자
function getAverageOfMaleAtOnce(data) {
    const onlyMales = data.filter(function (el) {
        return el.gender === 'male';
    });

    const numOfMales = onlyMales.length;

    const onlyMaleAges = onlyMales.map(function (el) {
        return el.age;
    }); // 오직 남라고 구성된 배열에서 나이를 배열로 리턴할 수 있다.

    const sumOfAges = onlyMales.reduce(function(acc, cur){
       acc += cur
        return acc;
    }, 0); // 오직 남자들의 나이만을 더했을 때

    return sumOfAges / numOfMales;
    // 남자들의 평균 나이
}

순차적으로 원하는 작업을 수행할 수 있습니다. 남성의 평균 나이를 구할 때 다양한 방법 을 통해서 결과를 구할 수 있습니다.

고차 함수 정리

고차 함수의 종류

  • Array.prototype.find (요소를 찾는)
  • Array.prototype.reduce (누적해서 더하는, 초기값은 0)
  • Array.prototype.filter (조건에 맞춰 걸러줘)

profile
메일은 매일 확인하고 있습니다. 궁금하신 부분이나 틀린 부분에 대한 지적사항이 있으시다면 언제든 편하게 연락 부탁드려요 :)

0개의 댓글