пиши на фронт
Эта статья не является оригинальной и должна быть предоставлена вам по вашему запросу (статья длиннее).
Оригинальный автор:Сае Ю
Оригинальная ссылка:debounce | throttle
Также существует множествогалантерейные товары😁
debounce
предисловие
При фронтенд-разработке будут встречаться некоторые частые триггеры событий, такие как:
-
window
изresize
,scroll
-
mousedown
,mousemove
-
keyup
,keydown
- ...
С этой целью давайте возьмем пример кода, чтобы понять, как часто срабатывает событие:
мы пишемindex.html
документ:
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
<title>debounce</title>
<style>
#container{
width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="debounce.js"></script>
</body>
</html>
debounce.js
Код файла следующий:
var count = 1;
var container = document.getElementById('container');
function getUserAction() {
container.innerHTML = count++;
};
container.onmousemove = getUserAction;
Давайте посмотрим на эффект:165 свайпов слева направо
getUserAction
функция!
Поскольку этот пример очень простой, браузер полностью отвечает, но что, если это сложная функция обратного вызова или запрос ajax? Предполагая, что за 1 секунду срабатывает 60 раз, каждый обратный вызов должен быть выполнен в течение 1000/60 = 16,67 мс, иначе возникнет задержка.
Для решения этой проблемы, как правило, есть два решения:
- опровергать
- дроссель
принцип
Принцип защиты от встряски: хотя вы запускаете событие, я должен выполнить его через n секунд после запуска события.Если вы запускаете событие в течение n секунд после запуска события, то я буду считать время нового события как стандарт. , я выполню его через n секунд. Короче говоря, я выполню его только после того, как вы инициируете событие и больше не будете запускать событие в течение n секунд. Это действительно преднамеренно!
выполнить
первое издание
Из этого утверждения мы можем написать код для первой версии:
// 第一版
function debounce(func, wait) {
var timeout;
return function () {
clearTimeout(timeout)
timeout = setTimeout(func, wait);
}
}
Если мы хотим его использовать, возьмем в качестве примера самое начало:
container.onmousemove = debounce(getUserAction, 1000);
Теперь двигайтесь, как хотите, в любом случае, после того, как вы двигаетесь, он больше не будет срабатывать в течение 1000 мс, и я выполню событие. Посмотрите на эффект от использования:Внезапно оно сократилось со 165 до 1!
Отлично, давайте перейдем к его совершенствованию.
Второе издание - это
если мы былиgetUserAction
в функцииconsole.log(this)
, без использованияdebounce
функция,this
Значение:
<div id="container"></div>
Но если мы воспользуемся нашимdebounce
функция,this
укажет наWindow
объект!
Поэтому нам нужноthis
указать на правильный объект.
Мы модифицируем код ниже:
// 第二版
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context)
}, wait);
}
}
Сейчасthis
Его уже можно указать правильно. Давайте рассмотрим следующий вопрос:
Третье издание - Объект события
JavaScript предоставляет объекты событий в обработчиках событийevent
, мы модифицируемgetUserAction
функция:
function getUserAction(e) {
console.log(e);
container.innerHTML = count++;
};
если мы не используемdebouce
функция, которая будет печатать здесьMouseEvent
объекта, как показано на рисунке:Но в нашей реализации
debounce
работает, но только печатаетundefined
!
Итак, давайте снова изменим код:
// 第三版
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
На данный момент мы исправили две незначительные проблемы:
this
направлениеevent
объект
Четвертое издание — выполнить сейчас
На данный момент код очень полный, но чтобы сделать эту функцию более полной, давайте подумаем о новом требовании.
Это требование:
Я не хочу ждать, пока событие перестанет срабатывать, я хочу, чтобы функция выполнялась немедленно, а затем ждала n секунд после того, как она перестанет срабатывать, прежде чем перезапустить выполнение.
Думать об этом спросе также очень разумно, тогда давайте добавимimmediate
Параметр определяет, следует ли выполнять немедленно.
// 第四版
function debounce(func, wait, immediate) {
var timeout;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
}
Посмотрим на эффект от использования:
Пятое издание — возвращаемые значения
Одна вещь, которую следует отметить в этот момент, заключается в том, чтоgetUserAction
Функция может иметь возвращаемое значение, поэтому мы также возвращаем результат выполнения функции, но когдаimmediate
дляfalse
, потому что с помощьюsetTimeout
,мы будемfunc.apply(context, args)
Возвращаемое значение присваивается переменной, и, наконец,return
, значение всегда будетundefined
, поэтому у нас есть толькоimmediate
дляtrue
возвращает результат выполнения функции.
// 第五版
function debounce(func, wait, immediate) {
var timeout, result;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
}
}
Окончательная версия - отменена
Наконец, давайте подумаем о небольшом требовании, я надеюсь отменитьdebounce
функция, говорю яdebounce
Интервал времени 10 секунд,immediate
дляtrue
, в этом случае я могу повторно запустить событие только через 10 секунд, и теперь я хочу, чтобы кнопка после нажатия отменяла анти-дрожание, чтобы я мог снова вызвать его, и его можно было выполнить немедленно, разве это не очень радостно?
Для этого требования пишем последнюю версию кода:
// 第六版
function debounce(func, wait, immediate) {
var timeout, result;
var debounced = function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
Итак, как использовать этоcancel
Что насчет функций? Тем не менее возьмите приведенную выше демонстрацию в качестве примера:
var count = 1;
var container = document.getElementById('container');
function getUserAction(e) {
container.innerHTML = count++;
};
var setUseAction = debounce(getUserAction, 10000, true);
container.onmousemove = setUseAction;
document.getElementById("button").addEventListener('click', function(){
setUseAction.cancel();
})
Эффект демонстрации следующий:До сих пор мы полностью реализовали
underscore
серединаdebounce
Функция, поздравления, посыпать цветы!
throttle
принцип
Принцип дросселирования прост:
Если вы продолжаете запускать событие время от времени, выполняйте его только один раз.
В зависимости от того, исполняется ли он в первый раз и исполняется ли после окончания, эффект разный, и способ выполнения разный.
мы используемleading
Представляет, выполняется ли он в первый раз,trailing
Следует ли выполнять снова после завершения делегата.
Что касается реализации дросселирования, есть две основные реализации: одна использует метки времени, а другая — устанавливает таймеры.
выполнить
Первое издание — использование временных меток
Рассмотрим первый способ: с помощью временной метки, когда событие срабатывает, мы вынимаем текущую временную метку, а затем вычитаем предыдущую временную метку (начальное значение равно 0), если она больше установленного периода времени, Просто выполните функцию, а затем обновите временную метку до текущей временной метки, если она меньше, не выполняйте.
После прочтения этого утверждения чувствуется, что вы уже можете писать код... Давайте напишем первую версию кода:
// 第一版
function throttle(func, wait) {
var context, args;
var previous = 0;
return function() {
var now = +new Date();
context = this;
args = arguments;
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
}
}
Примеры до сих порdebounce
например, если бы вы использовали:
container.onmousemove = throttle(getUserAction, 1000);
Демонстрация эффекта выглядит следующим образом:Мы видим, что при перемещении мыши событие выполняется немедленно, и оно будет выполняться один раз каждые 1 с.Если оно перестанет срабатывать на 4,2 с, событие не будет выполняться в будущем.
Второе издание — Использование таймеров
Далее поговорим о второй реализации, использующей таймер.
Когда событие срабатывает, мы устанавливаем таймер, а когда событие срабатывает снова, если таймер существует, он не будет выполняться до тех пор, пока не сработает таймер, а затем выполняется функция для очистки таймера, чтобы следующий можно установить таймер.
// 第二版
function throttle(func, wait) {
var timeout;
var previous = 0;
return function() {
context = this;
args = arguments;
if (!timeout) {
timeout = setTimeout(function(){
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
Чтобы сделать эффект более очевидным, мы установилиwait
время для3s
, эффект следующий:
Мы видим, что при перемещении мыши событие не будет выполнено сразу, после встряхивания в течение 3 секунд оно, наконец, выполняется один раз.3s
Выполнить один раз, когда число отображается как 3, немедленно перемещать мышь, что эквивалентно примерно 9.2s
Он перестает срабатывать, когда12s
когда событие выполняется.
Итак, сравните два метода:
- Первое событие будет выполнено немедленно, второе событие будет выполнено в первый раз через n секунд.
- После того, как первое событие перестанет срабатывать, повторное выполнение события будет невозможно.После того как второе событие перестанет срабатывать, событие все равно будет выполняться снова.
3-е издание - Два меча вместе
Итак, чего мы хотим?
Кто-то сказал: я хочу голову и хвост! То есть движение мыши может быть выполнено сразу, а может быть выполнено снова при остановке триггера!
Итак, мы объединяем преимущества двух, а затем объединяем два меча, чтобы написать версию кода:
// 第三版
function throttle(func, wait) {
var timeout, context, args, result;
var previous = 0;
var later = function() {
previous = +new Date();
timeout = null;
func.apply(context, args)
};
var throttled = function() {
var now = +new Date();
//下次触发 func 剩余的时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果没有剩余的时间了或者你改了系统时间
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}
Демонстрация эффекта выглядит следующим образом:Мы видим: мышь вдвигается, событие выполняется сразу, встряхивание на 3с, событие выполняется снова, когда число становится равным 3, то есть через 6с, мы сразу выдвигаем мышь и перестаем вызывать событие, и в 9 секунд оно все равно будет выполнено снова.Выполнить событие.
Четвертая редакция — Оптимизация
Но мне иногда хочется, чтобы не было головы и не было хвоста, или чтобы была голова и не было хвоста, что мне делать?
Затем мы устанавливаемoptions
В качестве третьего параметра, а затем определить, какой эффект основан на переданном значении, мы соглашаемся:
leading:false
Указывает, что первое выполнение отключеноtrailing: false
Указывает, что обратный вызов для прекращения стрельбы отключен
Давайте изменим код:
// 第四版
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
// 此处大佬的意思应该是给options一个默认值,我们可以用ES6语法写在函数声明上。
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
// 不知为何此处要加 if (!timeout) context = args = null;希望看到此处的大佬指教一下。
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}
Окончательная версия - отменена
существуетdebounce
В реализации мы добавляемcancel
метод,throttle
мы также добавляемcancel
метод:
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
};
return throttled;
}
Уведомление
мы должны обратить вниманиеunderscore
Есть такая проблема в реализации:
То есть leading:false
а такжеtrailing: false
нельзя установить одновременно.
Если установлено одновременно, например, когда вы перемещаете мышь, потому чтоtrailing
Установить какfalse
, таймер не будет установлен, когда триггер остановлен, поэтому, пока прошло установленное время, а затем вошел, он будет выполнен немедленно, что является нарушениемleading: false
,bug
вышел, так чтоthrottle
Есть только три использования:
container.onmousemove = throttle(getUserAction, 1000);
container.onmousemove = throttle(getUserAction, 1000, {
leading: false
});
container.onmousemove = throttle(getUserAction, 1000, {
trailing: false
});