일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 브라우저의 렌더링 과정
- const
- package management
- 자바스크립트 딥다이브
- 프로퍼티 어트리뷰트
- Babel과 Webpack
- 프론트엔드 성능 최적화 가이드
- 이미지 갤러리 최적화
- 인터넷 장비
- 자바스크립트
- 자바스크립트 패턴
- 올림픽 통계 서비스 최적화
- Set과 Map
- 이벤트
- 프로그래머스
- 빌트인 객체
- DOM
- ES6함수 추가기능
- 제너레이터와 async/await
- 블로그 서비스 최적화
- Property Attribute
- 딥다이브
- var 사용금지
- 비전공이지만 개발자로 먹고삽니다
- 모던 자바스크립트 Deep Dive
- 스코프
- peerdependencies
- 커리어
- 전역변수의문제점
- 디스트럭처링
- Today
- Total
Dev Blog
고차함수(Higher Order Function) 본문
특별한 대우를 받는 일급 객체(first-class citizen)인 함수는
세가지 특별 대우를 받습니다.
- 변수에 할당할 수 있다. assignment
- 다른 함수의 인자로 전달될 수 있다. argument
- 다른 함수의 결과로서 리턴될 수 있다. return
함수를 데이터(string, number, boolean, array, object)를 다루듯이 다룰 수 있다는 걸 의미합니다.
변수에 저장할 수 있기 때문에 배열의 요소나 객체의 속성값으로 저장하는 것도 가능합니다.
- 다른 함수를 인자로 받는 함수는 caller => 콜백함수를 호출(invoke)하여 실행시킵니다.
- 그 인자로 전달되는 함수를 callback 라고 합니다. => 호출되어 실행되는데 답신전화를 뜻합니다.
=> '배열' 데이터를 활용하는 메소드들로,
그 중 가장 많이 쓰이는 forEach, filter, map, reduce 을 기억해야 합니다.
그 이외에도 sort, some, every, find 가 있습니다.
forEach
forEach는 "실행" 하는 의미를 갖고 있기 때문에, 실행하는 경우(로그, API 호출 등)에 적합합니다.
forEach 메서드는 주어진 함수를 배열 요소 각각에 대해 실행합니다.
arr.forEach(callback(currentvalue[, index[, array]])[, thisArg])
화살표 함수를 이용한 forEach
var array = [1,2,3,4,5];
array.forEach((value, index, sourceArray) => {
sourceArray.push(10);
console.log(value);})
output : 1,2,3,4,5
array : [1,2,3,4,5,10,10,10,10,10]
예시1)
const animals = ["lion", "tiger"];
animals.forEach(animal => {
if(animal === 'lion') console.log("사자!")
else console.log('호랑이!');
});
// [콘솔 출력 결과]
// 사자!
// 호랑이!
예시2)
var array = [1,2,3,4,5];
function print(val, index) {
console.log(`array[${index}] = ${val}`);
};
array.forEach(print);
/* 출력결과
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5
*/
예시3) tree-map 구현 코드
var Tree = function(value) {
this.value = value;
this.children = [];
};
//child(가지)를 추가하는 함수
Tree.prototype.addChild = function(child) {
// your code here
child = new Tree(child);
this.children.push(child);
// return the tree for convenience
return this;
};
//트리 구현 함수.
//map() : 배열의 모든 '요소'들에 callback 함수를 적용하여 새로운 반환값으로 새로운 배열을 만든다.
//callback 함수는 (undefined도 포함해서) 배열 값이 들어있는 인덱스에 대해서만 호출된다.
// 이 문제에서는 value가 해당되는데, 모든 요소의 value를 방문하며 callback을 적용한다.
Tree.prototype.map = function(callback) {
// your code here
//map 이 실행되면, 함수 안에서 callback 이 실행된다.
//새로운 노드를 생성하고 callback함수를 통해 노드들의 value를 두배로 최신화 한다.
//새롭게 생성된 전체 노드를 반환한다.
const newTree = new Tree(callback(this.value));
//기존 Tree의 value에 callback 함수를 적용한다.
//this 는 기존의 tree(Tree) 를 말한다.
this.children.forEach(function(child){
child = newTree.children.push(child.map(callback))
//기존 트리(Tree)의 children 배열의 child 들은 각각 트리로서
//map함수를 적용받아 (recursion) newTree의 children 배열 안에
//담긴다(push).
})
return newTree;
};
forEach 구현 코드
_.each = function (collection, iteratee) {
// TODO: 여기에 코드를 작성합니다. 배열판 for문이다.
if(Array.isArray(collection)){
for(let i=0; i < collection.length; i++){
iteratee(collection[i], i, collection)
}
} else{
for (let el in collection){
iteratee(collection[el], el, collection)
}
}
//iteratee 는 callback 함수이고, 인자의 위치는 정해져 있다.
//if(배열일 경우), else(객체일 경우)
filter
filter 메서드는 주어진 함수의 테스트를 통과하는 모든 요소를 모아 새로운 배열로 반환합니다.
filter는 "테스트" 하는 의미를 갖고 있기 때문에, 특정 조건에 해당하는 요소를 뽑아내는 경우(a>N 등 if문)에 적합하다.
예시1)
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']
배열의 filter 메소드는 배열의 요소 중 특정 조건을 만족하는 요소들만을 걸러내는(filter) 메소드입니다.
예를 들어, 수(number)를 요소로 갖는 배열 중 짝수만을 걸러내거나, 18 보다 작은 수만을 걸러내는 식입니다. 문자열(string)을 요소로 갖는 배열 중 길이가 10 이하인 문자열만 걸러내거나, 'korea'만 걸러낼 수도 있습니다.
걸러내는 기준이 되는 특정 조건은 함수 형태로 filter 메소드의 인자로 전달되어야 합니다. filter 메소드는 (걸러내기 위한 조건을 명시한) 함수를 인자로 취하기 때문에 고차 함수입니다.
예시2)
// 함수 표현식
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']
예시3)
function clickedUserId(event){
const clickedId = event.target.textContent;
const filterdDATA = DATA.filter(function(el){
return el.user === clickedId;
})
state.isFiltered = true;
cleanDisplay();
renderFilteredTweets(filterdDATA);
}
filter 구현 코드
_.filter = function (arr, test) {
// TODO: 여기에 코드를 작성합니다.
let result = []
_.each(arr, function(item){
if(test(item)){
result.push(item)
}
})
return result;
};
메소드 동작방식
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 메소드는 인자로 전달되는 콜백 함수에 배열의 요소를 다시 전달합니다.
콜백 함수는 전달받은 배열의 요소를 받아 (조건에 따라) 참(true) 또는 거짓(false)을 리턴해야 합니다.
map
map 메서드는 배열 내의 모든 요소 각각에 대하여 주어진 함수를 호출한 결과를 모아 새로운 배열을 반환합니다
map은 "변환" 하는 의미를 갖고 있기 때문에, 각 요소를 변환하는 경우(a * N 등)에 적합합니다.
특이한 점은 forEach( )와 달리 실행결과를 모은 새 배열을 리턴한다는 것입니다.
때문에, map을 사용하기 위해서는 return 문이 있어야 합니다.
예시1)
const animals = ["lion", "tiger"];
result = animals.map(animal => {
return `안녕 나는 ${animal} 이라고 해`
});
console.log(result);
//Array [ "안녕 나는 lion 이라고 해", "안녕 나는 tiger 이라고 해" ]
map 구현 코드
_.map = function (arr, iteratee) {
// TODO: 여기에 코드를 작성합니다.
// _.map 함수는 매우 자주 사용됩니다.
// _.each 함수와 비슷하게 동작하지만, 각 요소에 iteratee를 적용한 결과를 리턴합니다.
let result = [];
_.each(arr, function(item){
result.push(iteratee(item))
})
return result;
};
reduce
reduce 메서드는 배열의 각 요소에 대해 주어진 리듀서(reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.
다른 함수들이 배열로 반환하는 것에 비해, reduce는 단일 값을 반환해야하는 경우(SUM 등)에 적합하다.
예시1) array of arrays 펼치기 flatten
var printArray = function(arr) {
function flattenArr(arr){
return arr.reduce(function(acc,cur){
//acc.concat() 안에 들어가는 값이
//숫자이거나 단일 배열일때까지 cur을 recursion 해준다.
//결국 acc.reduce의 결과값은 단일 배열이 된다.
return acc.concat(Array.isArray(cur)? flattenArr(cur) : cur)
},[])
};
let result = flattenArr(arr);
return result.join('')
};
printArray([1,2,3,[4,5,[6,7,[8]]]])
결과값: "12345678"
예시2) key-value map 역전
let cities = {
Lyon: 'France',
Berlin: 'Germany',
Paris: 'France'
};
let countries = Object.keys(cities).reduce((acc, k) => {
let country = cities[k];
acc[country] = [...(acc[country] || []), k];
return acc;
}, {});
/* 화살표 함수로 표현
let countries = Object.keys(cities).reduce(
(acc, k) => (acc[cities[k]] = [...(acc[cities[k]] || []), k], acc) , {});
*/
// countries is
{
France: ["Lyon", "Paris"],
Germany: ["Berlin"]
}
reduce 구현 코드
_.reduce = function (arr, iteratee, initVal) {
// TODO: 여기에 코드를 작성합니다.
let acc;
if(initVal !== undefined){
acc = initVal;
_.each(arr, function (el, idx){
let result = iteratee(acc, el, idx, arr);
acc = result;
})
} else {
let newArr = _.slice(arr, 1);
acc = arr[0];
_.each(newArr, function(el){
let result = iteratee(acc, el);
acc = result;
})
}
return acc;
}
Why use higher order function?
추상화를 위해서입니다.
이는 복잡한 어떤 것을 압축해서 핵심만 추출한 상태로 만드는 것인데 이렇게 사고하는 것이 효율적이고 편하기 때문입니다.
자바스크립트(를 비롯한 많은 프로그래밍 언어) 역시 추상화의 결과입니다. 컴퓨터를 구성하는 장치(CPU)는 0과 1만 이해합니다. 크롬 개발자 도구의 콘솔(console)탭에서 아래 코드를 입력했을 때, 어떤 과정을 거쳐 10이 출력되는지 몰라도 10을 출력할 수 있습니다. 그런 복잡한 것들은 크롬의 자바스크립트 해석기(엔진)가 대신 해주기 때문입니다.
즉 추상화란,
추상화 = 생산성(productivity)의 향상
추상화의 관점에서 함수를 바라보면, 함수는 사고(thought) 또는 논리(logic)의 묶음입니다.
function getAverage(data) {
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum = sum + data[i];
}
return sum / data.length;
}
let output = getAverage([1, 2, 3]);
console.log(output); // --> 2
output = getAverage([4, 2, 3, 6, 5, 4]);
console.log(output); // --> 4
getAverage 함수는 수(number)를 요소로 갖는 배열을 입력받아 요소의 평균값을 리턴합니다. 앞으로는 수를 요소로 갖는 배열을 인자로 전달하기만 하면 (조금? 복잡한 로직은 신경쓰지 않아도) 언제나 평균값을 얻게 됩니다.
위와 같은 경우는,
함수 = 값을 전달 받아 값을 리턴한다 = 값에 대한 복잡한 로직은 감추어져 있다 = 값 수준에서의 추상화
함수를 통해 얻은 추상화의 단계를 한단계 더 높인 것이 고차 함수입니다.
값 수준의 추상화: 단순히 값(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;
}
'남성'의 '평균 나이'만 구하는 작업에만 국한된 함수입니다. 개선점을 찾아보자면, 'male'을 매개변수화(parameterization)하여 조금 더 일반적인(generic) 함수로 변경할 수도 있습니다.
'남성' 중 '최연소 나이'를 구하거나, '여성' 중 '최연소 나이와 최연장 나이의 차이'를 구할 때 이미 작성된 로직을 그대로 쓸 수 있습니다. filter, map, reduce 등의 배열 메소드를 사용할 수 있습니다.
compose 함수는 입력받은 함수들을 순차적으로 결합하는 고차 함수입니다. 각각의 작업(filter, map, reduce)들은 별도의 함수로 분리되어, 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
위 고차함수들은 어떻게 작동하는가?
const getAverageAgeOfMale = compose(
getOnlyMales, // 배열을 입력받아 배열을 리턴하는 함수 -> 남자로 구성된 배열을 추출한다.
getOnlyAges, // 배열을 입력받아 배열을 리턴하는 함수 -> 남자의 나이로 된 배열을 추출한다.
getAverage // 배열을 입력받아 `number` 타입을 리턴하는 함수 -> 남자 나이의 평균을 구한다.
);
'BootCamp_Codestates > Pre Tech Blog' 카테고리의 다른 글
spread syntax VS rest parameter (0) | 2020.11.27 |
---|---|
알고리즘 - 수도코드 작성법 (0) | 2020.11.10 |
Closure (0) | 2020.11.08 |
Scope (0) | 2020.11.03 |
객체 (0) | 2020.11.03 |