개발공부/자바스크립트

[JavaScript] 스터디 8일차_ 클로저 (3) 활용(부분적용함수, 커링함수)

햄❤️ 2021. 7. 12. 16:26
728x90

클로저(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)

https://typeof-undefined.tistory.com/3

https://codingsalon.tistory.com/27

728x90