Микропаттерны оптимизации в Javascript: декораторы функций debouncing и throttling

    Декораторы функций позволяют добавить дополнительное поведение функции, не изменяя ее. Сигнатура оригинальной и декорированной функции полностью совпадают.


    Debouncing


    Если дословно переводить — «устранение дребезга». Такой декоратор позволяет превратить несколько вызовов функции в течение определенного времени в один вызов, причем задержка начинает заново отсчитываться с каждой новой попыткой вызова. Возможно два варианта:
    1. Реальный вызов происходит только в случае, если с момента последней попытки прошло время, большее или равное задержке.
    2. Реальный вызов происходит сразу, а все остальные попытки вызова игнорируются, пока не пройдет время, большее или равное задержке, отсчитанной от времени последней попытки.

    Использование


    debouncedFn = $.debounce(fn, timeout, [invokeAsap], [context]);
    • fn — оригинальная функция
    • timeout — задержка
    • invokeAsap — true/false, по умолчанию false. Параметр, указывающий какой из вышеперечисленных вариантов debouncing нужно использовать (по умолчанию используется первый)
    • context — контекст оригинальной функции

    Пример использования


    Например, у вас есть suggest. Посылать запросы к серверу на каждый keyup расточительно и не нужно. Можно декорировать обработчик, чтобы он срабатывал только после того, как пользователь перестал нажимать на клавиши, допустим, в течение 300 миллисекунд:
    function onKeyUp() { ... };

    $('input[name=suggest]').keyup($.debounce(onKeyUp, 300));


    * This source code was highlighted with Source Code Highlighter.

    Или у вас есть один обработчик на несколько событий, и нужно, чтобы если в течение некоторого времени происходит оба события, обработчик срабатывал только на последнем произошедшем событии:
    $('input').bind('keyup blur', $.debounce(process, 300));

    * This source code was highlighted with Source Code Highlighter.

    Throttling


    Данный декоратор позволяет «затормозить» функцию — функция будет выполняться не чаще одного раза в указанный период, даже если она будет вызвана много раз в течение этого периода. Т.е. все промежуточные вызовы будут игнорироваться.

    Использование


    throttledFn = $.throttle(fn, period, [context]);
    • fn — оригинальная функция
    • period — период
    • context — контекст оригинальной функции

    Пример использования


    Например, на resize окна (или, допустим, на mousemove) у вас срабатывает какой-то тяжелый обработчик. Можно его «затормозить»:
    $(window).resize($.throttle(doComplexСomputation, 300));
    В итоге, функция будет выполняться не чаще, чем раз в 300 миллисекунд.

    Мои реализации этих декораторов в виде jQuery-плагина можно скачать с code.google.

    Ps. Навеяно книжкой Николаса Закаса «Professional JavaScript for Web Developers, 2nd Edition», хотя в ней он путает debounce и throttle, называя первое вторым. На ajaxian тоже поднималась эта тема.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 27
    • –1
      очень полезно. спасибо
    • 0
      Спасибо. Полезные сведения и примеры отличные.
      • +3
        Круто, код становится прекрасным — обожаю JS :)
        • 0
          Идеи хорошие, паттерны — они такие, воздушные, казалось бы очевидные ;)
          Но вот можно было сделать реализацию на чистом js. Без этого пустовато.
          • 0
            Вероятно, было бы удобней реализовать в виде $(selector). debounceBind(type, data, fn), и в data подавать параметр задержки.

            Кстати, код можно оформить и по jsDoc.

            Не удалось адекватно повесить биндер на $.ajax.success, правда, задача не совсем жизненная: )

            В целом реализация хорошая. Спасибо.
            • 0
              debounce и bind могут применяться не только для обработчиков событий, поэтому делать debounceBind было бы странным. debounceBind может быть надстройкой над debounce.

              А что у вас именно не получилось с $.ajax.success?
              • 0
                Ну если нас интересует отслеживание выполнения функции в определенный период времени, то здесь имеет место быть контекст событийности.

                Не получилось реализовать чтото вроде:

                var onload = function(_e) {
                  console.log('loaded');
                };
                var test = function() {
                  $.ajax({
                    'url': 'http://myhost/blablabla',
                    'success': $.debounce(onload, 1000)
                  })
                }

                Если повесить test() на onClick кнопки, то функция будет выполняться каждый раз с этой задержкой, без фильтрации. Пример возник из невнятной идеи: запрос отправляется постоянно по событию а апдейт dom например происходит не чаще чем какойто интервал времени.
                • +2
                  Потому что на каждый клик у вас создается новый экземпляр задекорированной функции. Надо сделать так:

                  var onload = $.debounce(function(_e) {
                   console.log('loaded');
                  }, 1000);

                  var test = function() {
                   $.ajax({
                    'url': 'http://myhost/blablabla',
                    'success': onload
                   })
                  };


                  * This source code was highlighted with Source Code Highlighter.
            • –1
              Примеры хорошие, но было бы интересней иметь библиотеку манипуляции функциями — функции before/after, кэширование (которое зачем-то назвали debounce), может что-то ещё кому в голову придёт?
              • 0
                А при чем тут кеширование и debounce? у них идеи противоположные.
                • 0
                  Точно, я невнимательно прочитал. Ну вот и ещё одно применение — кэширование — доработке функций.
              • 0
                Может, стоит перенести в блог jQuery? Когда я начал читать, удивился, подумав что это реализовано в
                джаваскрипте по умолчанию, только ближе к концу стало очевидно, что это реализовано в виде плагина к jQuery.

                За статью спасибо, пригодиться в новом проекте.
                • 0
                  Написанное в статье справедливо для Javascript'а в целом, а не jQuery. Сам плагин легко переписывается без jQuery.
                  • 0
                    В коде вообще ничего нет от jQuery кроме того, что вы функции положили в его неймспейс. :) Поэтому не вижу никакого смысла оформлять это как плагин для jQuery.
                    • 0
                      Cмысл в том, что я использую jQuery в своей работе, и есть чувство, что такие «низкоуровневые» фичи имеет смысл тоже ложить в неймспейс фреймворка.
                • 0
                  Мой вариант debouncing: habrahabr.ru/blogs/webdev/17884/ (не знал тогда, что это так называется :)
                  • 0
                    У вас throttling, а не debouncing :)
                    • 0
                      Я их постоянно путаю, ну и придумали же названия :)
                  • –7
                    Поздравляю, вы только что переизобрели несколько комбинаторов из Flapjax (flapjax-lang.org):
                    — blindE (аналог debounce)
                    — calmE (аналог throttle)

                    Только вот Flapjax основан на мощной теоретической базе, и реализация довольно продвинутая.
                    • +2
                      Автор ничего не изобретал, он же пишет: «Навеяно книжкой Николаса Закаса».

                      А реализация модуля по мотивам удобного паттерна в различных библиотеках — только плюс разработчикам.
                      • 0
                        Я ничего не изобретал, я сделал свою жизнь, с точки зрения веб-разработчика немного проще и написал об этом. Если у вас мощная теоретическая база для этого — написали бы, я бы с удовольствием почитал.
                        • 0
                          Окей, напишу, как появится свободное время.
                      • 0
                        отлично. полезно… декораторы за частую развязывают руки…
                        • 0
                          спасибо, вы мне много времени сэкономили
                          • 0
                            Поддерживаю. Статье пять лет, а актуальность сохраняется. Столкнулся с описанной задачей, а ничего более свежего и доступного не нашел.

                            Под впечатлением от сабжа, сделал свою версию: github.com/lolmaus/jquery.timer-tools

                            Отличия от сабжа:

                            — Изменен порядок аргументов. Колбэк теперь идет последним, что гораздо удобнее (см. примеры).
                            — Добавлена функция delay — алиас для setTimeout с измененным порядком аргументов.
                            — Debounce с исполнением колбэка в начале или в конце серии вызовов разделен на две отдельные функции.
                            — Код throttle упрощен. Автор сабжа там перемудрил.
                            — Плагин досутпен через Bower.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.