일반적인 프로그래밍 언어에서 함수의 매개변수는 기본 유형 또는 객체 참조만 될 수 있으며, 반환 값은 기본 데이터 유형 또는 객체 참조만 될 수 있습니다. 그러나 Javascript에서 함수는 일급 시민이며 매개변수로 전달되거나 반환 값으로 반환될 수 있습니다. 소위 고차 함수(higher-order function)는 함수를 매개변수로 받거나 함수를 반환값으로 취할 수 있는 함수이다. 이 두 가지 상황에는 실제 개발에서 많은 응용 시나리오가 있습니다. 이 기사는 제가 직장과 연구에서 접한 몇 가지 응용 시나리오를 요약한 것입니다.
콜백 함수
코드 재사용은 애플리케이션을 측정하는 중요한 기준 중 하나입니다. 변경된 비즈니스 로직을 추출하여 콜백 함수에 캡슐화함으로써 코드 재사용률을 효과적으로 향상시킬 수 있습니다. 예를 들어, ES5의 배열에 추가된 forEach 메서드는 배열을 순회하고 각 요소에 대해 동일한 함수를 호출합니다.
array = {}; array.forEach = function(arr, fn){ for (var i = 0, len = arr.length; i < len; i++) { fn(arr[i], i, arr); } }
매번 순회 코드를 다시 작성할 필요 없이 콜백 함수에 비즈니스 초점을 맞춥니다.
일부 기능
함수를 반환값으로 출력하는 대표적인 응용으로 부분함수(partial function)가 있습니다. 소위 부분 함수란 다른 부분, 즉 매개변수나 변수가 미리 설정된 함수를 호출하는 함수를 생성하는 사용법을 말합니다. 어쨌든, 정의를 보면 이게 무슨 용도인지 이해가 안 되네요. 먼저 예제를 살펴보겠습니다. 부분 함수의 가장 일반적인 예는 유형 판단입니다.
Javascript 객체에는 프로토타입 속성, 클래스 속성, 확장성이라는 세 가지 속성이 있습니다. (모르는 학생들은 Rhino 책 138페이지를 다시 읽어보세요.) 클래스 속성은 문자열이며 Javascript에서는 직접 제공되지 않지만 Object.prototype.toString을 사용하여 간접적으로 얻을 수 있습니다. 이 함수는 항상 다음 형식을 반환합니다.
[객체 클래스]
이제 일련의 isType 함수를 작성할 수 있습니다.
코드는 다음과 같습니다.
isString = function(obj){ return Object.prototype.toString.call(obj) === "[object String]"; } isNumber = function(obj){ return Object.prototype.toString.call(obj) === "[object Number]"; } isArray = function(obj){ return Object.prototype.toString.call(obj) === "[object Array]"; }
이 함수의 대부분의 코드는 반복됩니다. 이때 고차 함수가 화려하게 데뷔합니다.
isType = function(type) { return function(obj) { return Object.prototype.toString.call(obj) === "[object " + type + "]"; } } isString = isType('String'); isNumber = isType('Number'); isArray = isType('Array');
그래서 일부 매개변수를 지정하여 새로운 맞춤형 함수를 반환하는 형태가 부분함수입니다.
카레
커링은 부분평가라고도 합니다. 커링 함수는 먼저 일부 매개변수를 승인한 후 즉시 평가하지 않지만 방금 전달된 매개변수는 함수에 의해 형성된 클로저에 저장됩니다. 함수가 실제로 평가될 때 전달된 모든 매개변수는 한 번에 평가에 사용됩니다.
var currying = function(fn) { var args = []; return function() { if (arguments.length === 0) { return fn.applay(this, args); } else { args = args.concat(arguments); return arguments.callee; } } }
한 달 동안의 일일 비용을 예로 들어 계산해 보겠습니다.
var currying = function(fn) { debugger; var args = []; return function() { if (arguments.length === 0) { return fn.apply(this, args); } else { Array.prototype.push.apply(args, arguments); return arguments.callee; } } } cost = function(){ var sum = 0; for (var i = 0, len = arguments.length; i < len; i++) { sum += arguments[i]; } return sum; } var cost = currying(cost); cost(100); cost(200); alert(cost())
이벤트 제한
일부 시나리오에서는 특정 이벤트가 반복적으로 실행될 수 있지만 이벤트 처리 기능을 매번 실행할 필요는 없습니다. 예를 들어, window.resize 이벤트에서 복잡한 논리 계산이 수행됩니다. 사용자가 브라우저 크기를 자주 변경하면 복잡한 계산이 성능에 심각한 영향을 미칠 수 있습니다. 때로는 rezise가 발생할 때마다 이러한 논리 계산을 트리거할 필요가 없습니다. 제한된 계산만 필요합니다. 현재로서는 기간에 따른 일부 이벤트 요청을 무시해야 합니다. 다음 제한 기능을 살펴보세요.
function throttle(fn, interval) { var doing = false; return function() { if (doing) { return; } doing = true; fn.apply(this, arguments); setTimeout(function() { doing = false; }, interval); } } window.onresize = throttle(function(){ console.log('execute'); }, 500);
함수 실행 시간을 제어하면 함수 실행 횟수와 기능 요구 사항 간의 완벽한 균형을 이룰 수 있습니다. 또 다른 이벤트는 mousemove입니다. 이 이벤트를 DOM 요소에 바인딩하면 마우스가 해당 요소 위로 이동할 때 이벤트가 반복적으로 트리거됩니다.
이벤트가 종료되었습니다
자주 발생하는 일부 이벤트의 경우 이벤트 종료 후 일련의 작업을 수행하고 싶을 때가 있습니다. 이때 고차 함수를 사용하여 다음 처리를 수행할 수 있습니다.
function debounce(fn, interval) { var timer = null; function delay() { var target = this; var args = arguments; return setTimeout(function(){ fn.apply(target, args); }, interval); } return function() { if (timer) { clearTimeout(timer); } timer = delay.apply(this, arguments); } }; window.onresize = throttle(function(){ console.log('resize end'); }, 500);
이 과정에서 이벤트가 발생하면 마지막 이벤트 핸들을 지우고 실행 시간을 다시 바인딩합니다.
참고:
《노드에 대한 자세한 설명》
"Javascript 디자인 패턴 및 개발 방식"