Декораторы — это функции высшего порядка, которые принимают в качестве аргумента одну функцию и возвращают другую. Возвращаемая функция является преобразованным вариантом функции-аргумента Javascript Allongé
Давайте самостоятельно напишем некоторые базовые дектораторы, представленные в таких библиотеках, как underscore.js, lodash.js, ramda.js.
once()
Скопировать ссылку
- once(fn) создает экземпляр функции, которая должна быть выполнена только один раз. Паттерн может быть использован, например, для инициализации, когда нужно быть уверенным в единичном запуске функциональности, даже если сама функция вызвана в нескольких местах.
function once(fn){
let returnValue;
let canRun = true;
return function runOnce(){
if(canRun) {
returnValue = fn.apply(this, arguments);
canRun = false;
}
return returnValue;
}
}
var processonce = once(process);
processonce(); // process
processonce(); //
Функция once() возвращает другую функцию — runOnce(), использующую замыкание. Обратите также внимание, как осуществлен вызов оригинальной функции, а именно через передачу this и arguments в метод apply: fn.apply(this, arguments).
Если хотите узнать замыкания глубже, обратите внимание на статью «Why you should give the Closure function another chance».
after() Скопировать ссылку
- after(count, fn) создает вариант функции, которая будет выполнена только после определенного количества вызовов. Функция полезна, например, если должна быть выполнена только по завершению асинхронных операций.
function after(count, fn) {
let runCount = 0;
return function runAfter() {
runCount = runCount + 1;
if (runCount >= count) {
return fn.apply(this, arguments);
}
}
}
function logResult() { console.log('calls have finished'); }
let logResultAfter2Calls = after(2, logResult);
setTimeout(function logFirstCall() {
console.log('1st call has finished');
logResultAfter2Calls();
}, 3000);
setTimeout(function logSecondCall() {
console.log('2nd call has finished');
logResultAfter2Calls();
}, 4000);
В примере выше при помощи after() я создаю функцию logResultAfter2Calls(). Она в свою очередь выполняет logResult() только после второго вызова.
throttle() Скопировать ссылку
- throttle(fn, wait) создает вариант функции, которая при повторяющихся вызовах выполняется через указанный временной интервал (аргумент wait). Декоратор эффективен для обработки быстро повторяющихся событий.
function throttle(fn, interval) {
let lastTime;
return function throttled() {
let timeSinceLastExecution = Date.now() - lastTime;
if(!lastTime || (timeSinceLastExecution >= interval)) {
fn.apply(this, arguments);
lastTime = Date.now();
}
};
}
let throttledProcess = throttle(process, 1000);
$(window).mousemove(throttledProcess);
Здесь движение мыши генерирует множество событий mousemove, тогда как оригинальная функция process() вызывается лишь раз в секунду.
debounce() Скопировать ссылку
- debounce(fn, wait) создает вариант функции, которая выполняет оригинальную функцию спустя wait миллисекунд после предыдущего вызова декорированной функции. Паттерн также применяется в работе с повторяющимися событиями. Он полезен, если функциональность должна быть выполнена по завершению очереди событий.
function debounce(fn, interval) {
let timer;
return function debounced() {
clearTimeout(timer);
let args = arguments;
let that = this;
timer = setTimeout(function callOriginalFn() {
fn.apply(that, args);
}, interval);
};
}
let delayProcess = debounce(process, 400);
$(window).resize(delayProcess);
Функция debounce() часто используется вместе с событиями scroll, resize, mousemove и keypress.
Частичное применение Скопировать ссылку
Частичное применение преобразует функцию за счет изменения количества параметров. Это один из примеров движения от общего к частному.
partial() Скопировать ссылку
На этот раз создадим метод partial() и сделаем его доступным для всех функций. В данном примере я использую синтаксис ECMAScript 6, а именно оператор rest. С его помощью набор аргументов функции преобразуется в массив ...leftArguments
. Это нужно для конкатенации массивов, тогда как специальный объект arguments массивом не является.
function.prototype.partial = function(...leftArguments){
let fn = this;
return function partialFn(...rightArguments){
let args = leftArguments.concat(rightArguments);
return fn.apply(this, args);
}
}
function log(level, message){
console.log(level + ' : ' + message);
}
let logInfo = log.partial('Info');
logInfo('here is a message');
Обратите внимание, созданная таким образом logInfo() использует лишь один аргумент message.
Заключение Скопировать ссылку
Применение указанных функций помогает понять принципы работы декораторов и саму идею инкапсуляции логики внутри них.
Декораторы — мощный инструмент расширения функциональности без изменения исходной функции. Это отличный путь переиспользовать код, и он соответствует функциональной парадигме программирования.