코딩쌀롱

[코어 자바스크립트] 5-3 클로저의 활용 사례(부분 적용 함수, 커링 함수) 본문

[코어 자바스크립트] 5-3 클로저의 활용 사례(부분 적용 함수, 커링 함수)

이브✱ 2020. 12. 29. 16:44

부분 적용 함수(partially applied function)

 부분 적용 함수란 n개의 인자를 받는 함수에 미리 m개의 인자만 넘겨 기억시켰다가, 나중에 n-m개의 인자를 넘기면 비로소 원래 함수의 실행 결과를 얻을 수 있게끔 하는 함수이다.

 

this를 바인딩해야 하는 점을 제외하면 bind 메서드의 실행 결과가 바로 부분 적용 함수다. this를 사용하지 않는다면 bind 메서드만으로 문제없이 구현되겠지만 this의 값을 변경(null)할 수 밖에 없기 때문에 메서드에서는 사용할 수 없다. bind와 다르게 this에 관여하지 않는 부분 적용 함수가 있다면 더 좋을 것이다.

 

 

1) 부분 적용 함수 구현(1)

 첫 번째 인자로 원본 함수, 두 번째 인자 이후부터는 미리 적용할 인자들을 전달하고, 반환할 함수(부분 적용 함수)에서는 다시 나머지 인자들을 받아 이들을 모아(concat) 원본 함수를 호출(apply)한다. 실행 시점의 this를 그대로 반영함으로써 this에는 아무런 영향을 주지 않게 된다.

 다만 반드시 인자를 앞에서부터 차례로 전달할 수 밖에 없다. 인자들을 원하는 위치에 미리 넣어놓고 나중에 빈 자리에 인자를 채워넣어 실행할 수 있도록 코드를 수정해보자.

 

 

2) 부분 적용 함수 구현(2)

 '비워놓음'을 표시하기 위해 미리 전역객체에 _라는 프로퍼티를 준비하면서 삭제 변경 등의 접근에 대한 방어 차원에서 여러 가지 프로퍼티 속성을 설정했다. 17번~21번 줄에서 예시(1)과 차이가 있다. 처음에 넘겨준 인자들 중 _로 비워놓은 공간마다 나중에 넘어온 인자들이 차례대로 끼워 들어가도록 구현했다. 

 

 

3) 부분 적용 함수 - 디바운스

디바운스는 짧은 시간 동안 동일한 이벤트가 많이 발생할 경우 이를 전부 처리하지 않고 처음 또는 마지막에 발생한 이벤트에 대해 한 번만 처리하는 것으로, 프론트엔드 성능 최적화에 큰 도움을 주는 기능 중 하나이다. scroll, wheel, mousemove, resize 등에 적용하기 좋다.

 

최소한의 기능으로 구현을 해본다면,

 최초 이벤트가 발생하면 8번째 줄에 의해 timeout의 대기열에 'wait 시간 뒤에 func를 실행할 것'이라는 내용이 담긴다. 그런데 wait 시간이 경과하기 이전에 다시 동일한 event가 발생하면 이번에는 7번째 줄에 의해 앞서 저장했던 대기열을 초기화하고, 다시 8번째 줄에서 새로운 대기열을 등록한다. 결국 각 이벤트가 바로 이전 이벤트로부터 wait 시간 이내에 발생하는 한 마지막에 발생한 이벤트만이 초기화되지 않고 무사히 실행될 것이다.

 

콘솔로 실행 결과를 확인해보면

이렇게 실제 이벤트 발생(wait시간 내)은 여러번이지만 이벤트 발생에 따른 콜백함수 실행은 한 번씩 된다.

 

커링 함수(currying function)

커링 함수란 여러 개의 인자를 받는 함수를 하나의 인자만 받는 함수로 나눠서 순차적으로 호출될 수 있게 체인 형태로 구성한 것을 말한다. 부분 적용 함수와 기본적인 맥락은 일치하지만 몇 가지 차이가 있다.

 

부분 적용 함수 커링 함수
여러 개의 인자를 전달할 수 있고,
실행 결과를 재실행할 때 원본 함수 무조건 실행
한 번에 하나의 인자만 전달하는 것이 원칙.
중간 과정상의 함수를 실행한 결과는 그 다음 인자를 받기 위해 대기.
마지막 인자가 전달되기 전까지는 원본 함수가 실행되지 않음

 

 부분 적용 함수와 달리 커링 함수는 필요한 상황에 직접 만들어 쓰기 용이하다. 필요한 인자 개수만큼 함수를 만들어 계속 리턴해주다가 마지막에 짠! 하고 조합해서 리턴해주기 때문이다.  

 

 각 단계에서 받은 인자들을 모두 마지막 단계에서 참조할 것이므로 GC되지 않고 메모리에 차곡차곡 쌓였다가, 마지막 호출로 실행 컨텍스트가 종료된 후에야 비로소 한꺼번에 GC의 수거 대상이 된다.

 

 

1) 커링 함수(1)

getMaxWith10 는  function(b) { return Math.max(10, b) } 과 같다.

10과 비교해서 큰 수를 찾는 것을 여러 번 해야 한다면 커링함수를 이용해서 getMaxWith10으로 간단하게 마지막 인수 하나만 추가해 원하는 결과를 얻을 수 있다.

 

 

2) 커링 함수(2)

 인자가 많아질수록 가독성이 떨어진다는 단점이 있다. ES6의 화살표 함수(curry5_)를 사용하면 한 줄에 표기할 수 있다. 그리고 커링 함수를 이해하기 훨씬 수월하다. 화살표 순서에 따라 함수에 값을 차례로 넘겨주면 마지막에 func가 호출될 거라는 흐름이 한눈에 파악된다.

 

 이 커링 함수가 유용한 경우가 있다. 당장 필요한 정보만 받아서 전달하고 또 필요한 정보가 들어오면 전달하는 식으로 하면 결국 마지막 인자가 넘어갈 때까지 함수 실행을 미루는 셈이 된다. 이를 함수형 프로그래밍에서는 지연실행(lazy execution)이라고 칭한다. 원하는 시점까지 지연시켰다가 실행시키는 것이 요긴한 상황이라면 커링을 쓰기에 적합하다. 혹은 프로젝트 내에서 자주 쓰이는 함수의 매개변수가 항상 비슷하고 일부만 바뀌는 경우에도 유용하다. 

 

 

3) 커링 함수(3)

 fetch 함수는 url을 받아 해당 url에 HTTP 요청을 한다. 보통 REST API를 이용할 경우 baseUrl은 몇 개로 고정되지만 path나 id값은 매우 많을 수 있다. 이런 상황에서 서버에 정보를 요청할 필요가 있을 때마다 매번 baseUrl부터 전부 기입해주기보다는 공통적인 요소는 먼저 기억시켜두고 특정한 값(id)만으로 서버 요청을 수행하는 함수를 만들어두는 편이 개발 효율성이나 가독성 측면에서 더 좋을 것이다.

 

최근의 여러 프레임워크나 라이브러리 등에서 커링을 광범위하게 사용하고 있다. Flux 아키텍처의 구현체 중 하나인 Redux의 미들웨어를 예로 들면 다음과 같다.

 

4) Redux의 미들웨어의 커링 함수

 위 두 미들웨어는 공통적으로 store, next, action 순서대로 인자를 받는다. 이 중 store는 프로젝트 내에서 한 번 생성된 이후로는 바뀌지 않는 속성이고, dispatch의 의미를 가지는 next 역시 마찬가지지만, action의 경우는 매번 달라진다. 그러니까 store, next 값이 결정되면 logger, thunk에 store, next를 미리 넘겨서 반환된 함수를 저장시켜놓고, 이후에 action만 받아서 처리할 수 있게끔 한 것이다.

 

 

 

 


정재남 작가님의〔 코어 자바스크립트 〕책을 공부하며 정리했습니다.

Comments