Контекст

Има много събития в JS, които се задействат в миг на око.

Когато превъртате страницата, преоразмерявате прозореца или дори премествате мишката си, браузърът улавя десетки и десетки събития в секунда.

В много случаи обаче не е необходимо да заснемете всички междинни стъпки, защото обикновено се интересувате само от заснемането на крайното състояние (когато потребителят е приключил с превъртането или преоразмеряването на прозореца).

Debouncing е стратегия, която ни позволява да подобрим ефективността, като изчакаме да изтече определен период от време, преди да задействаме събитие. Така че, когато потребителят спре да задейства събитие, нашият код се изпълнява.

В повечето случаи това не е необходимо. Но ако са включени мрежови заявки или DOM промени (напр. изобразяване на компонент), тази техника може значително да подобри плавността на вашето приложение.

Използване

const handleScroll = debounce((event) => {
  // Do something every time a user scrolls
  console.log("Scrolling!");
}, 250);
window.addEventListener('scroll', handleScroll);

Обяснение

Тази функция не е най-лесната за увиване, особено ако не сте свикнали с функционалното програмиране! Разбира се, можете да използвате тази функция, без да я разбирате, но ако сте любопитни и трябва да разберем как работи:

const debounce = (callback, wait) => {
  let timeoutId = null;
  // Takes any number of arguments using spread syntax
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      callback.apply(null, args);
    }, wait);
  };
}

Нашата функция debounce приема два аргумента: функция и продължителност в милисекунди.
Искаме debounce() сам да върне функция. Функциите, които връщат функции, винаги нараняват мозъка ми, но ето как обичам да мисля за това:

Мислете за това като за готвач, който създава рецепта за приготвяне на ястие. Когато готвачът завърши рецептата, той може или да сервира ястието, или да го предаде на друг готвач. В случай на debounce(), все едно първият готвач е предал рецептата на втори готвач, който след това може да я използва, за да сготви ново ястие.

Вторият готвач може да промени рецептата, да добави нови съставки или дори да я използва като основа, за да създаде напълно ново ястие. По същия начин върнатата функция може да модифицира оригиналната функция, да добави нова функционалност или да я използва като основа за създаване на нова функция.

Забележете, че първият ред на тази функция инициализира променлива, **timeoutId. Този ред се изпълнява само веднъж. Планираме да извикаме нашата обвиваща функция няколко пъти, но извикваме само debounce() в началото.

При всяко извикване на функцията за обвивка се случват две неща:

  1. Ние отменяме всеки съществуващ setTimeout
    Така че, първият път, когато потребителят превърта, тази първа стъпка няма ефект, тъй като няма активно време за изчакване. За щастие, clearTimeout е много прощаваща функция; дори и да няма setTimeout в ход, той не се оплаква. Това е без операция - не прави нищо.
  2. Планираме setTimeout за продължителност, определена от аргумента за изчакване. Когато времето за изчакване изтече, ние извикваме нашата функция за обратно извикване с apply, предавайки всички аргументи.
    Веднъж програмиран, setTimeout връща число, препратка към въпросното изчакване. Ние съхраняваме това в нашето свойство timeoutId, за да можем да го изчистим по-късно, ако е необходимо. Тъй като тази променлива се инстанцира в предоставената функция, която е дефинирана извън обхвата на нашата обвиваща функция, тя продължава.

Да приемем, че потребителят е приключил с превъртането. Минават няколко милисекунди (съответстващи на дадения параметър за изчакване) и нашата обвивка се извиква отново.

Този път timeoutId сочи към планирано време за изчакване, така че първата стъпка го отменя, преди да насрочи ново.

Ако потребителят превърти отново, този цикъл ще се повтаря безкрайно. Много тайм-аути са планирани и незабавно отменени. Планирането и почистването на изчакване е много бърза операция с ниски разходи за памет, така че не е нужно да се тревожим за цената.

Заключение

Последният фрагмент в компактен израз:

const debounce = (callback, wait) => {
  let timeoutId = null;
  return (...args) => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      callback.apply(null, args);
    }, wait);
  };
}

Първоначално публикувано вhttps://blog.melvinvmegen.com/snippets/debounce на 26 май 2023 г.