클로저(1) 개념 및 원리 게시글 보러가기
클로저(2) 활용 게시글 보러가기
3. 부분적용함수(Partial application)
부분적용함수(Partially applied function)란 n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 (n-m)개의 인자를 넘기면 비로소 원래 함수의 실행결과를 얻을 수 있게끔 하는 함수
⭐ 여러개의 인자를 전달할 수 있고, 실행 결과를 재실행할 때 원본 함수가 무조건 실행된다.
🌞 부분적용함수를 왜 쓸까?
-> 미리 일부 인자를 넘겨두어 기억하게끔하고 추후 필요한 시점에 기억했던 인자들까지 함께 실행하고 싶을 때
3-1) 부분적용함수 예제_1번
- bind 메서드를 통해 this와 함수에 미리 넘길 인수를 일부 지정해서 새로운 함수를 만든다.
var add = function() {
var result = 0;
for (var i = 0; i < arguments.length; i++){
result += arguments[i];
}
return result;
}
var addPartial = add.bind(null, 1,2,3,4,5); // bind를 통해 인자 5개를 미리 적용(this는 사용되지 않는다)
console.log(addPartial(6,7,8,9,10)); //추가 5개를 추가적으로 전달하면 모든 인자를 모아 실행
//55출력
3-1) 부분적용함수 예제_2번
- 첫번째 인자로 원본함수(partialArgs), 두번째 인자 이후부터는 미리 적용할 인자들을 전달하고, 반환할 함수(부분적용함수 = func)에서는 다시 나머지 인자들을 받아 이들을 모아(concat) 원본 함수를 호출(apply) 한다.
실행시점의 this를 그대로 반영하여 this에는 영향을 주지 않는다.
var partial = function(){
var originalPartialArgs = arguments;
var func = originalPartialArgs[0];
if(typeof func !== 'function'){
throw new Error('첫 번째 인자가 함수가 아닙니다.');
}
return function(){
//외부함수의 arguments를 함수 빼고 두 번째 인자부터 배열로 만듬
//첫 번째 인자 배열 = 원본 함수
var partialArgs = Array.prototype.slice.call(originalPartialArgs, 1);
//두 번째 인자 배열 = 나머지 인자들
var restArgs = Array.prototype.slice.call(arguments); //현재 함수의 arguments
return func.apply(this, partialArgs.concat(restArgs));
// 인자를 합쳐서 func 실행, 여러 개의 인자들을 하나의 배열로 보냄(apply)!
// func에서는 배열 자체가 아니라 배열 속 원소들을 인자로 받음
};
};
var add = function() {
var result = 0;
for (var i = 0; i < arguments.length; i++){
result += arguments[i];
}
return result;
}
var addPartial = partial(add, 1,2,3,4,5);
console.log(addPartial(6,7,8,9,10)); //55출력
var dog = {
name: '멍멍이',
greet: partial(function(prefix, suffix) {
return prefix + this.name + suffix;
}, '왈왈, ')
}
dog.greet('입니다!'); //왈왈, 강아지 입니다!
3-1) 부분적용함수 예제_3번 (디바운스)
- 디바운스
짧은 시간 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것으로, 프런트엔드 성능 최적화에 큰 도움을 주는 기능 중 하나이다.
scroll, wheel, mousemove, resize 등에 적용할 수 있다.
(🌈오호,, Lodash 라이브러리로 무한 스크롤을 구현했을때 디바운스를 써봤던 기억이 있다!)
- 최초 이벤트가 발행하면 timeout 대기열에 wait 시간 뒤 func를 실행할 것이다. 그러나 wait 시간이 경과하기 이전에 다시 동일한 event가 발생하면 clearTimeout열에 의해 대기열을 초기화하고, 다시 timeoutId에 의해 새로운 대기열을 등록한다. 각 이벤트가 바로 이전 이벤트로부터 wait 시간 이내에 발생하는 한, 마지막 발생한 이벤트는 초기화되지 않고 실행될 것이다.
const debounce = function(eventName, func, wait){
let timeoutId = null;
return function (event) {
const self = this; //this를 self라는 변수에 별도로 담아준다
console.log(eventName, 'event 발생');
clearTimeout(timeoutId); //timeoutId 취소 = 초기화
timeoutId = setTimeout(func.bind(self, event), wait);
//bind해주지 않으면, setTimeout의 객체인 window가 this로 바인딩 된다.
};
};
const moveHandler = function (e) {
console.log('move event 처리');
};
const wheelHandler = function (e) {
console.log('wheel event 처리');
}
//이벤트가 발생하면 debounce 함수가 실행되고, 내부함수를 리턴, 반환된 내부함수가 이벤트 핸들러가 된다.
document.body.addEventListener('mouse', debounce('move', moveHandler, 500));
document.body.addEventListener('mouse', debounce('move', wheelHandler, 700));
실행결과
4. 커링함수(Currying)
커링함수란(currying function)란 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것. 한 번에 하나의 인자만 전달하는 것이 원칙
⭐마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않는다.
🌞 커링함수를 왜 쓸까?
-> 원하는 시점까지 지연시켰다가 실행하는 것이 유리한 상황(지연실행, lazy execution), Redux의 미들웨어가 대표적인 예시이다.
🌞 지연실행?
-> 당장 필요한 정보만 받아서 전달하고 또 필요한 정보가 들어오면 전달하는 식으로 마지막 인자가 넘어갈 때까지 함수 실행을 미루는 방식
4-1) 커링함수 예제_1번
- 필요한 인자 갯수만큼 함수를 만들어 계속 리턴하다가 마지막에만 조합하여 리턴하면 된다.
단, 인자가 많아질수록 가독성이 떨어진다.
- getMaxWith10은 function(b) {return Math.max(10, b)}을 의미한다. 값 10은 매개변수 a에 할당되고, 세개의 인자 중(함수,a,b) 마지막으로 필요한 매개변수인 b를 넘겨 두 값(a,b)를 비교해 최댓값을 출력한다.
- 변동가능성이 낮은 인자(a)를 먼저 받고, 변동가능성이 높은 인자(b)를 뒷 순서에 받는 것이 좋다.
var curry3 = function(func) {
return function(a) {
return function(b){
return func(a,b);
}
}
};
var getMaxWith10 = curry3(Math.max)(10);
console.log(getMaxWith10(8)); //8과 10을 비교해 큰 수인 10이 출력
console.log(getMaxWith10(25)); //10과 25를 비교해 큰 수인 25가 출력
var getMinWith10 = curry3(Math.min)(10);
console.log(getMinWith10(8)); // 10과 8을 비교해 더 작은 수인 8이 출력
console.log(getMinWith10(25)); //10과 25를 비교해 더 작은 수인 10이 출력
4-1) 커링함수 예제_2번
- 인자를 5개만 보내(a,b,c,d,e)만 보내 처리했음에도 코드가 13줄이나 된다. 길고 가독성이 떨어진다.
ES6에서는 화살표 함수를 써서 한 줄에 표기할 수 있다.
- 각 단계에서 받은 인자들은 모두 마지막 단계에서 참조될 것이므로 GC(가비지 컬렉터)의 수거 대상이 되지 않고 메모리에 쌓이다가 마지막 호출로 실행 컨텍스트가 종료된 후에 한꺼번에 GC 수거 대상이 된다.
var curry5 = function(func) {
return function(a) {
return function(b) {
return function(c) {
return function(d) {
return function(e) {
return func(a,b,c,d,e);
};
};
};
};
};
};
var getMax = curry5(Math.max);
console.log(getMax(1)(2)(3)(4)(5)); //5 출력
//ES6 화살표함수
var curry5_ = func => a => b => c => d =>e => func(a,b,c,d,e);
4-1) 커링함수 예제_3번
- fetch 함수는 url을 받아 해당 url에 HTTP 요청을 한다. baseUrl의 값은 몇 개로 고정되는 반면 path, id값은 많을 수 있다. 이 경우 매번 baseUrl부터 전부 기입하기보다는, 공통요소인 baseUrl은 먼저 기억시켜두고, 특정값인 id만으로 서버 요청을 수행하는 함수를 만들어두면 효율성 및 가독성에 유리하다.
const getInformation = baseUrl => path => id => fetch(baseUrl + path + '/' + id);
//url 전달 -> baseUrl - 첫번째 인자
const imageUrl = "http://imageAddress.com/";
const getImage = getInformation(imageUrl);
//path 전달 -> 두번째 인자
const getEmoticon = getImage('emoticon');
const getIcon = getImage('icon');
//실제 요청 (마지막 id 전달) -> 세번째 인자(원본 함수 실행)
const emoticon1 = getEmoticon(100);
const icon1 = getIcon(205);
const icon2 = getIcon(233);
4-1) 커링함수 예제_4번 (Redux middleware)
- 아래 두 미들웨어는 공통적으로 store, next, action 순서로 인자를 받는다. store는 바뀌지 않는 속성이고, action은 매번 달라지는 속성이다. 즉 logger와 thunk에 store,next를 미리 넘겨서 반환된 함수를 저장하고, action만 받아서 처리할 수 있다.
const logger = store => next => action => {
console.log('dispatching', action);
console.log('next state', store.getState());
return next(action);
}
//Redux Middleware 'thunk'
const thunk = store => next => action => {
return typeof action === 'function'
? action(dispatch, store.getState)
: next(action);
참고 자료
정재남, 『코어자바스크립트』, 위키북스(2019)
'개발공부 > 자바스크립트' 카테고리의 다른 글
[JavaScript] 스터디 9일차_ 프로토타입(2) 체인 (0) | 2021.07.12 |
---|---|
[JavaScript] 스터디 9일차_ 프로토타입(1) 개념 및 constructor (0) | 2021.07.12 |
[JavaScript] 스터디 7일차_ 클로저 (2) 활용 (0) | 2021.07.01 |
[JavaScript] 스터디 6일차_ 클로저 (1) 개념 및 메모리 관리 (0) | 2021.06.28 |
[JavaScript] 스터디 5일차_ 콜백 함수 (0) | 2021.06.23 |