자바스크립트

고차함수에 대한 전반적인 개념

nickbegain 2021. 5. 7. 16:15

자바스크립트에도 특별한 대우를 받는 일급 객체(first-class citizen) : 함수

→ 자바스크립트에서 함수는 아래와 같이 특별하게 취급

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

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

3. 다른 함수의 결과로서 리턴될 수 있다.

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

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

→ 변수에 함수를 할당 : 함수 표현식, 화살표 함수

→ 함수 표현식(function expression)은 함수 선언식(function declaration)과 다르게 호이스팅(Hoisting)이 적용되지 않습니다.

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

/  파싱과정에서 함수 선언을 최상단으로 끌어올려 선언하는 방식(코드의 위치는 유지)

/  코드가 실행되는 과정에서 함수 선언부를 코드의 최상단으로 끌어올리는 것처럼 보이게 합니다.

→ 함수 선언식의 호이스팅에 지나치게 의존하면, 코드의 유지 보수가 쉽지 않습니다

→ 함수 선언식은 어느 위치에나 함수를 선언할 수 있고, 함수의 실행 위치도 중요하지 않습니다

→  함수 표현식은 함수의 할당과 실행의 위치에 따라 결과가 달라지기 때문에, 코드의 위치를 어느 정도 예측할 수 있습니다. 

→ 함수는 변수에 저장된 데이터를 인자로 받거나, 리턴 값으로 사용할 수 있습니다. 

→ 함수도 변수에 저장될 수 있기 때문에 함수를 인자로 받거나, 리턴 값으로 사용할 수 있습니다

고차 함수란

→ 고차 함수(higher order function)는 함수를 인자(argument)로 받을 수 있고, 함수의 형태로 리턴할 수 있는 함수입니다.

→ 함수 내부에서 변수에 함수를 할당할 수 있으며 함수는 이 변수를 리턴할 수 있습니다.

→ 함수 내부에서 변수에 할당하지 않고 함수를 바로 이용할 수 있습니다. 

→ 고차 함수에 함수를 인자로 전달하고, 고차 함수는 함수 자체를 리턴

→ 콜백함수 : 다른 함수(caller)의 인자(argument)로 전달되는 함수를 콜백 함수(callback function)라고 합니다.

→ 콜백 함수의 이름은, 어떤 작업이 완료되었을 때 호출하는 경우가 많아서, 답신 전화를 뜻하는 콜백이라는 이름이 붙여졌습니다.

→ 콜백 함수를 전달받은 고차 함수는, 함수 내부에서 이 콜백 함

수를 호출(invoke) 할 수 있습니다

caller는 조건에 따라 콜백 함수의 실행 여부를 결정할 수 있습니다 (caller는 콜백함수를 파라미터로 받는 함수)

→ 커링함수 : '함수를 리턴하는 함수'

→  커링 함수라는 용어를 사용하는 경우에는, 고차 함수란 용어를 '함수를 인자로 받는 함수'에만 한정해 사용

→ 정확하게 구분하자면, 고차 함수가 커리 함수를 포함, 즉 '함수를 리턴하는 함수'와 '함수를 인자로 받는 함수' 모두, 용어를 고차 함수로 사용

→ 클로저의 한 유형이 커링함수(둘이 다른 개념이 아님)

--------------------------------------------------------------

* map() 메서드는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환합니다.  /  기존 배열은 그대로 유지된다

ex) 배열의 각 요소에 함수적용, 그 결과값을 다시 요소로 저장

const array1 = [1, 4, 9, 16];

 

// pass a function to map

const map1 = array1.map(x => x * 2);

 

console.log(map1);

// expected output: Array [2, 8, 18, 32]

--------------------------------------------------------------

* filter() 메서드는 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환합니다.  /  기존 배열은 그대로 유지

ex) 각 요소에 함수 적용, 반환 값이 true인 element만 요소로 다시 재 할당해 배열 return

// 함수 표현식

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]

--------------------------------------------------------------

[함수를 인자로 받고, 함수를 리턴하는 경우]

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

----------------------------------------------------------------------------------------------------------------

[함수와 수(num)를 입력받아 num에 함수를 두 번(twice) 적용(apply)한 결과를 리턴]

function applyTwice(func, num) {

 return func(func(num));

}

[두 개의 함수를 입력받아 두 함수가 결합된 새로운 함수를 리턴해야 합니다.]

function compose2(func1, func2) {

 return function(num) {

   return func1(func2(num));

 }

}

[함수들을 입력받아 함수들이 입력된 차례대로 결합된 새로운 함수를 리턴해야 합니다.]

function pipe() {

 // arguments 객체를 배열로 변환한다.

 let argsArr = Array.prototype.slice.call(arguments);

 return function(num) {

   let temp = num;

    for(let i=0;i<argsArr.length;i++) {

      temp = argsArr[i](temp)

    }

   return temp;

 }

 }

----------------------------------------------------------------------------------------------------------------

[내장된 고차함수]

→ 기본적으로 내장된 고차함수 : 배열 메소드들 중 일부가 대표적인 고차함수에 해당

(ex)  filter 메서드 )

배열의 filter 메소드는, 모든 배열의 요소 중에서 특정 조건을 만족하는 요소를 걸러내는 메소드

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

let output = arr.filter(짝수);

// 짝수 자리에 함수가 들어간다. 짝수일 경우에만 true를 반환한다.

console.log(output); // ->> [2, 4]

→ 걸러내는 기준이 되는 특정 조건은 filter 메소드의 인자로 전달

→ 전달되는 조건은 함수의 형태입니다

→ filter 메소드는, 걸러내기 위한 조건을 명시한 함수를 인자로 받기 때문에 고차함수

→ filter 메소드는 배열의 요소를, 인자(파라미터)로 전달되는 콜백 함수에 다시 전달

→ 콜백 함수는 전달받은 배열의 요소를 받아 함수를 실행하고, 콜백 함수 내부의 조건에 따라 참(true) 또는 거짓(false)을 리턴  /  여기서 콜백 함수는 filter메서드의 파라미터

→ 자바스크립트 배열 메소드 중 고차 함수 : forEach, find, filter, map, reduce, sort, some, every

→ filter 메소드에 들어가는 콜백 함수는 truthy 또는 falsy를 리턴할 수 있지만 

filter 메소드에 들어가는 콜백 함수는 Deep equality를 통해 조건을 명확하게 밝히는 걸 권장

----------------------------------------------------------------------------------------------------------------

- reduce

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

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

ex) reduce() 메서드에 초기값을 지정해주면 초기값,배열element순서대로 함수가 실행되며 결과값을 누산한다

/  초기값이 없는 경우 초기값은 배열의 0번째 인덱스로 지정되어 배열 element들만 함수가 실행되어 결과값을 누산한다.

const array1 = [1, 2, 3, 4];

const reducer = (accumulator, currentValue) => accumulator + currentValue;

 

// 1 + 2 + 3 + 4

console.log(array1.reduce(reducer));

// expected output: 10

 

// 5 + 1 + 2 + 3 + 4

console.log(array1.reduce(reducer, 5));

// expected output: 15

 

 

배열을 문자열로

 

배열을 객체로 (1개의 객체로 합친다)

 

----------------------------------------------------------------------------------------------------------------

[정수를 요소로 갖는 배열과 정수(num)를 입력받아 num을 배열에 추가하고 정렬한다고 가정할 경우, num의 인덱스를 리턴해야 합니다.]

function getIndex(arr, num) {

 // TODO: 여기에 코드를 작성합니다.

 if(arr.length === 0return 0;

 else {

   arr.push(num);

   // num보다 작은 경우 : 앞에 위치  

-> arr길이 - num보다 크거나 같은 배열길이 = num앞 el개수

   // num위치는 'arr길이 - num보다 크거나 같은 배열길이 + 1' 이므로 

인덱스는 arr길이 - num보다 크거나 같은 배열길이

 

   // filter는 num과 같거나 큰 element들만 걸러냄

   let filterFunc = function(args) {

     return num <= args;

   }

 

   // num보다 크거나 같은 배열

   let tempArr = arr.filter(filterFunc);

   // 현재 num의 위치 : arr길이 - num보다 크거나 같은 배열길이

   return arr.length - tempArr.length;

 }

 

}

[객체와 키를 입력받아 키에 해당하는 값이 배열인 경우, 100 보다 작은 요소들만 갖는 배열을 리턴해야 합니다.]

function lessThan100(number) {

 return number < 100;

}

 

function getElementsLessThan100AtProperty(obj, property) {

 // TODO: 여기에 코드를 작성합니다.

 if(property in obj && Array.isArray(obj[property])){

   // 이 배열에는 boolean값도 포함이 된다.(boolean은 number와 대소비교 가능)

   let temp = obj[property].filter(lessThan100);

   // 새로 filter함수 작성(return 반드시 붙일것!)

   return temp.filter(function(el){

     return typeof el === 'number';

   });

 }

 else return [];

}

----------------------------------------------------------------------------------------------------------------

- 추상화

→ 추상화는 복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것이 추상화입니다.  /  추상화를 이용하면, 효율적이고 편하게 생각할 수 있기 때문

→ 예를 들어 우리는 '-1'이라는 문자를 보고, -1은 0보다 1만큼 작은 수라고 설명할 수 있습니다  /  '-1'을 표현하는 현실의 방법은 존재하지 않습니다

→ ex) 브라우저 창에 주소를 입력했을 때, 어떤 일이 일어나는지 정확하게 알고 있지 않아도 우리는 그저 주소창에 올바른 주소를 입력하면, 브라우저가 해당 사이트를 보여 준다는 것만 알고 있어도 된다

/  입력한 내용을 전파하고, 어디 서버로 갔다가 다른 서버로 가는 등 그런 복잡한 내용은 사용자가 알필요가 없다.

→ 자동차의 시동 버튼, 자료를 정리하는 엑셀, 지하철/버스를 타기 위한 교통 카드도 추상화의 결과

→ 자바스크립트를 비롯한 많은 프로그래밍 언어 역시, 추상화의 결과

→ 컴퓨터를 구성하는 장치(중앙처리장치, CPU; Central Processing Unit)는 0과 1만 이해하며 개발자는 크롬 개발자 도구의 콘솔(console) 탭에서 다음의 코드를 입력했을 때, 어떤 과정을 거쳐 10이 출력되는지 몰라도 10을 출력할 수 있습니다

/ 복잡한 것들은 크롬의 자바스크립트 해석기(엔진)가 대신해 주기 때문입니다.

/ 해당 문법을 기계어(0,1로 구성)로 해석해서 컴퓨터에 명령어 전달 -> 추상화로 생략됨

/  컴퓨터의 내부 구조에 대한 고민이 추상화로 해결

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

→ 로그램을 작성할 때, 자주 반복해서 사용하는 로직은 별도의 함수로 작성

/  함수의 내부 내용은 몰라도 어떠한 것을 반환하는지만 알면 함수의 인자를 맞춰 가져다가 사용하면 된다 : 추상화

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

ex) getAverage 함수는 값(배열)을 전달받아, 이 값을 가지고 복잡한 작업을 수행

→ 이는 값 수준에서의 추상화

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

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

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

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

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

 

[여러개의 함수를 인자로 받아서 처리하는 함수(spread문법 사용)]

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;

  };

}

 

[객체 배열인 경우 reduce 사용시 특이사항]

// reduce

 // sum에 0 초기화 필요

 // -> 만약 객체로 이루어진 배열인 경우 초기화가 없을 경우 객체가 sum에 초기화가 된다.

 return temp.reduce(function(sum, el){

   return sum + el.score;

 },0);

 

→ 객체 배열인 경우 각 element에 접근하기 위해서 map()을 이용해서 접근 후 

‘return 파라미터;’ 하면 각 element를 수정후 변경된 element들로 구성된 배열이 반환

----------------------------------------------------------------------------------------------------------------

[학생의 정보가 담긴 객체를 요소로 갖는 배열을 입력받아 아래 조건에 맞게 변형된 배열을 리턴해야 합니다.

  1. 남학생들의 정보는 리턴하는 배열에서 제외합니다.
  2. 'grades' 속성값은 평균값(number 타입)으로 바꿉니다.]

인자 1 : students

  • 객체를 요소로 갖는 배열
  • arr[i]'name', 'gender' 등의 속성을 갖는 객체
  • 'grades' 속성은 number 타입을 요소로 갖는 배열
  • 'grades' 속성이 빈 배열인 경우는 없다고 가정합니다.

입출력 예시

let studentList = [

  {

    name: 'Anna',

    gender: 'female',

    grades: [4.5, 3.5, 4],

  },

  {

    name: 'Dennis',

    gender: 'male',

    country: 'Germany',

    grades: [5, 1.5, 4],

  },

  {

    name: 'Martha',

    gender: 'female',

    grades: [5, 4, 4, 3],

  },

  {

    name: 'Brock',

    gender: 'male',

    grades: [4, 3, 2],

  },

];

 

let output = studentReports(studentList);

 

console.log(output); // -->

[

  { name: 'Anna', gender: 'female', grades: 4 },

  { name: 'Martha', gender: 'female', grades: 4 },

];

 

[코드]

function studentReports(students) {

 // TODO: 여기에 코드를 작성합니다.

 

 // 1. 여학생나누기

 let femaleStudent = students.filter(function(el){

   return el.gender === 'female';

 });

 

 // 2. 평균값 구하기

 return femaleStudent.map(function(el){

   // 3. 평균값을 넣어줌

   // 배열의 각 객체요소에 접근해서 해당 객체의 속성값 변경

   el.grades = el.grades.reduce(function(sum,avgs){

     // 내부함수에서 외부함수에서 사용하는 매개변수를 사용할 수 있음(클로저)

return sum+avgs / el.grades.length;

   },0);

 

   return el

 });

 

}

----------------------------------------------------------------------------------------------------------------

→ 2차원 배열을 1차원 배열로 변경하는 경우

 

 let oneDimensionArr = 2차원배열명.reduce(function(result,args){

   return result = [...result,...args];

 },[]);

→ spread 문법사용, 초기값은 ‘[]’로 설정하여 배열로 합친다.

→ 초기값은 내가 원하는 형태의 초기값을 주는 것이 좋음

ex) 배열은 [] , number은 0, string은 ‘’, 객체는 {}

→ reduce()를 이용하여 배열의 최대값을 구하는 식 

----------------------------------------------------------------------------------------------------------------

고차함수는 다음과 같은 특징을 가지고 있습니다.

  • 다른 함수를 인자로 받는 경우
  • 함수를 리턴하는 경우
  • 위 두가지 모두에 해당하는 경우

unshift는 위 세가지에 모두 해당하지 않기 때문에 고차함수가 아닙니다.

Array.prototype.find  /  Array.prototype.reduce  /  Array.prototype.filter 는 고차함수