Как и зачем использовать throttle и debounce из underscore или lodash
Про _.throttle и _.debounce написано немало. Но не хочу кидать ссылки на описание этих функций в официальной документации - их Вы и без меня найдете. Повинуясь старой привычке, я наглядно покажу, какие проблемы помогают разрешить означенные функции.
Это одни из тех приемов, что я очень советую знать и использовать во фронтенд-разработке. Можете даже сразу посмотреть демо. Только не забудьте открыть консоль, чтобы понять, в чем дело. А всех желающих разобраться с этой темой подробнее прошу под кат.
Про throttle
Многим из нас приходилось делать какие-то вещи, например, при изменении размера окна или скроллинге. Все мы видели, а может, и делали сами симпатичные лэндинги, где при ресайзе окна выполнялись прикольные преобразования, какая-то анимация, интересные эффекты и прочее. И задействовать приходилось javascript, ведь не все можно соорудить с помощью только css
Подобные операции красивы и нравятся нашим пользователям, но к сожалению, дают большую нагрузку на браузер и dom. Давайте для примера напишем простенький обработчик события ресайза окна.
var _onResizeWindow = function() { console.log('width: ', $(window).width()); } $(window).on('resize', _onResizeWindow);
Этот код всего лишь выводит в консоль ширину окна. Посмотреть можно здесь - демо-страничка. В блоке throttle выберите пункт "Простой ресайз окна". Попробуйте поиграть размером окна, и увидите, что эта функция срабатывает десятки раз даже на небольшое изменение ширины.
А теперь представьте, что в _onResizeWindow у нас написан не безобидный console.log, а ряд операций по сложным манипуляциям с dom - браузер взвоет, а иногда и начнет тормозить отрисовку. Возможно, Вам это покажется маловероятным, ведь у всех давно уже не третьи пентиумы. Но я не раз убеждался, что одной причин многих тормозов на сайтах является неоптимальная работа вот с такими незаметными мелочами, вроде скроллинга и ресайза. А ведь чтобы снизить нагрузку на браузер, нужно сделать самую малость - использовать _.throttle
// Функция, выполняющаяся при ресайзе окна var _onResizeWindow = function() { console.log('width: ', $window.width()); } // Та же функция, только с _.throttle var _onResizeWindowThrottled = _.throttle(_onResizeWindow, 300); $(window).on('resize', _onResizeWindowThrottled);
_.throttle создает обертку для искомой функции и дает ей указание запускаться не чаще, чем раз в N миллисекунд. В нашем случае укажем 300 - это стандартный промежуток времени, за который пользователи не замечают задержку интерфейса, а браузер успевает выполнить нужные манипуляции с dom. Конечно, без фанатизма, если на каждый чих перерисовывать все содержимое страницы, никакие ухищрения не помогут :-)
Вернемся к демо-странице. Выберите "Ресайз окна с _.throttle" и убедитесь, что функция срабатывает гораздо реже (для демонстрации установлено значение 500). Таким образом, как бы часто не вызывалось некоторое событие, _.throttle не даст нужной функции запускаться слишком часто и создавать лишнюю нагрузку на браузер.
Про debounce
А теперь рассмотрим еще один пример, связанный с производительностью фронтенда, и разберемся с _.debounce.
Представим, что у нас на сайте есть некое поле ввода, правильность данных в котором мы хотим проверять в режиме онлайн. Не когда пользователь кликнет на кнопку "Отправить", а прямо в процессе набора, чтобы как можно раньше сообщить ему об ошибке (конечно, нужно делать это ненавязчиво).
Рассмотрим конкретный пример: есть поле ввода email-а, на которое навешано событие keyup, запускающее проверку на валидность email-адреса. И если адрес не валиден, то выдаем сообщение об ошибке. Для примера мы выведем сообщение в консоль, причем с любым результатом проверки - опять же для наглядности.
// Валидация email var isValidEmail = function(email) { return (/^([a-z0-9_\-]+\.)*[a-z0-9_\-]+@([a-z0-9][a-z0-9\-]*[a-z0-9]\.)+[a-z]{2,4}$/i).test(email); } // Валидация при вводе email var _onKeyupEmailInput = function() { var email = $('input').val(), isValid = isValidEmail(email), message = email + ' is ' + isValid; if (isValid) { console.info(message); } else { console.error(message); } } $('input').on('keyup', _onKeyupEmailInput);
На функцию isValidEmail не обращайте внимания, регулярка по-быстрому нагуглена в интернетах (впрочем, она работает достаточно четко). Посмотреть пример можно все на той же страничке демо. В разделе debounce выберите пункт "Простое нажатие кнопок". Теперь откройте консоль и начните набирать какой-нибудь email. В консоль сразу посыпятся ошибки console.error ровно до тех пор, пока не введете валидную почту.
И здесь видим 2 проблемы.
Во-первых, не стоит отталкивать пользователей и сообщать им об ошибке на каждую нажатую кнопку. Ведь чаще всего он не ошибается, а просто не может мгновенно напечатать требуемую строку. Не нужно лишний раз отвлекать людей от процесса.
А вторая проблема еще хуже: допустим, мы не проверяем почту, а пишем сервис онлайн-переводов, вроде google translate. И делаем нужные проверки не на клиенте, а например, бомбим ajax-запросами на сервер. Что конечно же требует гораздо больше времени ожидания и впустую потраченных ресурсов сервера. Это когда вместо одной проверки на test@gmail.com сервер будет заниматься t, te, tes, test ... и так далее до искомого test@gmail.com.
В обоих случаях нам поможет такой прием: запускать нужную функцию (_onKeyupEmailInput) не сразу, а только спустя некоторое время после срабатывания события в браузере (у нас keyup). Это поможет посетителю спокойно набирать email, а когда он закончит и перестанет щелкать по клавишам, запустится функция валидации, которая уже и определит правильность введенного email-а. В любом случае при таком подходе гораздо больше вероятность, что код будет работать с теми данными, которые пользователь хочет нам сообщить, а не с наполовину введенными.
Надеюсь, проблема и способ решения понятны, теперь посмотрим, как это делается в коде - до ужаса похоже на пример с _.throttle
... var _onKeyupEmailInputDebounced = _.debounce(_onKeyupEmailInput, timeDebounce); $('input').on('keyup', _onKeyupEmailInputDebounced);
Добавили функцию-обертку и привязали ее в input keyup. Посмотрите, в чем отличия на демо-странице - выберите "Нажатие кнопок с _.debounce".
И это все, что я хотел рассказать о замечательных функциях throttle и debounce. Надеюсь, что убедил в разумности использования таких приемов на практике, и эти функции станут Вашими незаменимыми помощниками.
Ссылки из статьи
Демо приложения
Исходники -
исходники страницы демо, более развернутые, чем написано в статье.
Что еще почитать о фронтенде
- 10+ способов оптимизации фронтенда
- Пишем встраиваемый виджет на нативном javascript и php
- jquery промисы на примерах
- tabulator.js - интерактивная таблица за 10 минут
- History API на одностраничных сайтах
- javascript-шаблонизация для начинающих на примере lodash template
Истории из жизни айти и обсуждение кода.