Throttling и debouncing — паттерны ограничения частоты вызовов функций. В контексте timeago.js они применяются для управления частотой обновлений и реакции на события (scroll, resize, WebSocket).
render при изменении локали.Debounce откладывает вызов до тех пор, пока события не прекратятся на N миллисекунд. Полезно для ситуаций, когда данные приходят пакетами.
function debounce(fn, delay) {
let timer;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
Применение при получении данных через WebSocket:
import { render, cancel } from 'timeago.js';
const refreshTimeElements = debounce(() => {
const nodes = document.querySelectorAll('[datetime]');
cancel(nodes);
render(nodes, 'ru');
}, 300);
socket.onmess age = () => {
// ... добавить элемент в DOM ...
refreshTimeElements(); // один вызов после серии сообщений
};
Throttle ограничивает частоту вызовов независимо от интенсивности событий.
function throttle(fn, interval) {
let lastCall = 0;
return function(...args) {
const now = Date.now();
if (now - lastCall >= interval) {
lastCall = now;
return fn.apply(this, args);
}
};
}
Применение при прокрутке:
import { render, cancel } from 'timeago.js';
const onScr oll = throttle(() => {
const visibleNodes = Array.from(
document.querySelectorAll('[datetime]')
).filter(el => {
const rect = el.getBoundingClientRect();
return rect.top < window.innerHeight && rect.bottom > 0;
});
cancel(document.querySelectorAll('[datetime]'));
render(visibleNodes, 'ru');
}, 500);
window.addEventListener('scroll', onScroll, { passive: true });
При динамическом переключении языка пользователь может быстро менять локаль:
import { cancel, render } from 'timeago.js';
const applyLocale = debounce((locale) => {
const nodes = document.querySelectorAll('[datetime]');
cancel(nodes);
render(nodes, locale);
}, 200);
localeSelector.addEventListener('change', (e) => {
applyLocale(e.target.value);
});
При возвращении на вкладку не нужно обновлять чаще раза в 5 секунд:
import { cancel, render } from 'timeago.js';
const refreshOnFocus = throttle(() => {
const nodes = document.querySelectorAll('[datetime]');
cancel(nodes);
render(nodes, 'ru');
}, 5000);
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
refreshOnFocus();
}
});
MutationObserver вызывается очень часто — debounce снижает нагрузку:
import { render } from 'timeago.js';
const pendingElements = new Set();
const flushPending = debounce(() => {
if (pendingElements.size > 0) {
render(Array.from(pendingElements), 'ru');
pendingElements.clear();
}
}, 100);
const mo = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== Node.ELEMENT_NODE) return;
node.querySelectorAll('[datetime]').forEach(el => pendingElements.add(el));
});
});
flushPending();
});
mo.observe(document.body, { childList: true, subtree: true });
import { debounce, throttle } from 'lodash-es';
import { render, cancel } from 'timeago.js';
const debouncedRefresh = debounce((locale) => {
const nodes = document.querySelectorAll('[datetime]');
cancel(nodes);
render(nodes, locale);
}, 300);
const throttledVisibilityRefresh = throttle(() => {
const nodes = document.querySelectorAll('[datetime]');
cancel(nodes);
render(nodes, 'ru');
}, 1000);
| Ситуация | Подход |
|---|---|
| Пакет WebSocket-сообщений | Debounce |
| Смена локали при наборе | Debounce |
| Scroll-событие | Throttle |
| Visibility change (вкладка) | Throttle |
| Форс-обновление по кнопке | Throttle |
| Пакетное добавление DOM-элементов | Debounce |