Эта статья участвовала в "Проект «Звезда раскопок»”, чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.
Это 117-я оригинальная статья без воды.Если вы хотите получить больше оригинальных статей, выполните поиск в официальном аккаунте и подпишитесь на нас~ Эта статья была впервые опубликована в блоге Zhengcaiyun:Сценарии и приложения для предотвращения дрожания
Сценарии и приложения для предотвращения дрожания
задний план
В повседневной разработке мы часто сталкиваемся с поисковыми запросами, в процессе ввода пользователь инициирует изменение значения Input и продолжает инициировать вызов функции. Или, когда пользователь проводит пальцем, чтобы просмотреть продукты на странице поиска продукта, если мы отслеживаем прокрутку окна для отправки запроса на отслеживание, интерфейсные вызовы будут запускаться часто. Но иногда мы не хотим часто запускать интерфейсные вызовы во время непрерывной работы пользователя. Чтобы ограничить возникновение таких высокочастотных вызовов триггерных функций в течение короткого периода времени, мы можем использовать защиту от сотрясений и дросселирование.
Функция защиты от сотрясения и дросселирования обеспечивают эффект оптимизации эффективности выполнения функции за счет управления частотой срабатывания событий. Давайте сначала посмотрим на разницу между обычным, антивибрационным и дроссельным на следующей картинке.
По оси абсцисс на рисунке — ось времени, видно, что анти-шейк — это выполнение коллбэка после того, как событие перестанет срабатывать в течение определенного промежутка времени, а троттлинг — это выполнение коллбэка через определенный интервал времени, когда событие продолжается. для запуска.
Анализ сцен с защитой от сотрясений и дросселирования
Стабилизатор
Anti-shake, как следует из названия, предотвращает дрожание. Он используется для преобразования триггера поведения пользователя в триггер поведения программы, чтобы предотвратить изменение результата операции пользователя. В течение определенного периода времени событие выполняется несколько раз в течение указанного нами интервала в n секунд, а обратный вызов будет выполнен только один раз.
Особенности: дождавшись остановки определенной операции, работайте через определенные промежутки времени.
- Непрерывный триггер не выполняется
- Выполнить через период времени, который не срабатывает
Сценарии применения:
mousemove
событие скольжения мыши
// 首次不立即执行
function debounce(func, wait) {
let timer;
return function () {
const context = this;
const args = arguments;
clearTimeout(timer);
timer = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
function getUserAction(e) {
// container 为示例代码容器
container.innerHTML = `${e.clientX},${e.clientY}`;
};
container.onmousemove = debounce(getUserAction, 1000);
Эффект защиты от сотрясений не добавляется, как показано на рисунке Во время процесса скольжения мыши координаты x и y постоянно меняются и отображаются.
После добавления эффекта стабилизации, как показано на рисунке, через 1000 мс после того, как мышь перестала скользить, обновляются координаты осей x и y.
Далее рассмотрим пример, который часто встречается в бизнесе, Select переходит к функции динамического поиска на стороне сервера. Отличие от приведенного выше сценария в том, выполняется ли он в первый раз
// 立刻执行第一次函数,给用户展示默认的 m 条数据,等到用户手动输入停止触发 n 秒后,再重新执行
function debounce(func, wait, immediate) {
let timer;
let localImmediate = immediate;
return function () {
const context = this;
const args = arguments;
if (localImmediate) {
// 标记为,用于标记第一次是否立即执行
localImmediate = false;
func.apply(context, args);
}
clearTimeout(timer);
timer = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
function fetchData(vaule) {
// 调用接口请求数据
}
debounce(fetchData, n);
-
Вышеупомянутое предназначено для реализации защиты от сотрясения после того, как пользователь перестанет вводить данные на n секунд, а затем перейдет к серверу для запроса данных, но возможно, что пользовательский ввод
杭州
После запуска первого поиска. Затем вошел市体育馆
, который запускает второй поиск.В это время на странице могут отображаться две ситуации:
- Первый результат поиска возвращается быстрее второго и будет отображаться первым
杭州
результаты поиска, а затем отображать杭州市体育馆
результаты поиска. Эффект показан на рисунке: - Первый результат поиска возвращается медленнее, чем второй, и будет отображаться первым.
杭州市体育馆
результаты поиска, а затем отображать杭州
результаты поиска. Эффект показан на рисунке:
На самом деле ни первый кейс, ни второй кейс не очень хороши, надеемся, что сразу отобразится один раз
杭州市体育馆
результаты поиска.Итак, как с этим бороться? Мы можем просто установить переменную для отметки последнего запроса и отображать возвращенный результат пользователю только тогда, когда отметка текущего запроса интерфейса равна самой последней отметке.
- Первый результат поиска возвращается быстрее второго и будет отображаться первым
/**
* 每次调用 fetchData 方法,更新全局变量 this.lastFetchId 并赋值给内部变量 fetchId。
* 每个 fetchData 方法内部逻辑,在接口成功返回后判断内部变量 fetchId 是否与全局变量
* this.lastFetchId 是否相等,若相等才进行赋值,反之不改变数据。
*/
// 全局变量,标记最新的请求 id,每次调用 fetchData 时更新
this.lastFetchId = 0;
function fetchData(value) {
const { searchField, params = {}, url, dataKey } = this.props;
const [data, setData] = useState([]);
const [fetching, setFetching] = useState(false);
this.lastFetchId += 1;
// 每个方法调用的内部变量 fetchId
const fetchId = this.lastFetchId;
setData([]);
setFetching(true);
const postValue = typeof value === 'string' ? value : '';
params[searchField] = postValue;
request(url, {
method: 'post',
data: params,
}).then((res) => {
const { success, result } = res || {};
// 如果不是最新请求,那么不进行结果赋值
if (fetchId !== this.lastFetchId) {
return;
}
if (success && Array.isArray(result)) {
setData(result);
setFetching(false);
}
});
};
В приведенном выше примере показано, что функция защиты от сотрясения позволяет избежать ошибочного принятия одной операции за несколько операций и ограничивает верхний предел выполнения события, то есть оно будет выполнено только через n секунд после остановки триггера. Тот же сценарий может также включать операции отправки форм, такие как вход и регистрация, и пользователь слишком быстро щелкает, чтобы инициировать несколько запросов, а редактирование содержимого, такого как электронные письма редактора форматированного текста, сохраняется в режиме реального времени.
дросселирование
Дросселирование, по определению, потока управления. Частота пользовательского события возникает, когда сценарий интерактивного управления страницей обычно представляет собой операцию синхронизации или другие единицы временного интервала. Период времени, когда событие достигает триггера каждый временной интервал n секунд после того, как мы установили.
Особенности: Выждав определенный интервал, выполните операцию
- Запускается постоянно и не будет выполняться несколько раз
- В определенное время/другой интервал (например, высота слайда) для выполнения
Сценарий приложения (Примечание: поскольку в следующих примерах используется бизнес-контент компании, фактические скриншоты страниц не отображаются):
- Похороните сцену. Список поиска товаров, окно товаров и т. д. Когда пользователь проводит пальцем, высота смахивания фиксируется в фиксированное время, и отправляется запрос на скрытую точку.
// 不立即执行,在 n 秒后第一次执行事件,事件停止触发后会再执行一次
// 假设设置的时间间隔为 1s,如果在第 6.8s 停止触发,那么在第 6s 时执行一次,
// 第 7s 时会再继续执行最后一次
function throttle(func, wait) {
var timer;
return function() {
var context = this;
var args = arguments;
if (!timer) {
timer = setTimeout(function(){
timer = null;
func.apply(context, args)
}, wait)
}
}
}
function sendData(vaule) {
// 调用接口发送数据
}
throttle(sendData, n);
Как показано на рисунке, запрос скрытых точек отправляется через равные промежутки времени.
- Когда система эксплуатации и обслуживания просматривает журнал работы приложения, он обновляется каждые n секунд.
// 立即执行,在 n 秒后第一次执行事件,事件停止触发后会不会再执行
// 特点:假设设置的时间间隔为 1s,如果在第 6.8s 停止触发,那么在第 6s 时执行最后一次,之后不会再执行
function throttle(func, wait) {
var previous = 0;
return function() {
// 隐式转换
var now = +new Date();
var context = this;
var args = arguments;
if (now - previous > wait) {
previous = now;
func.apply(context, args);
}
}
}
function fetchLogData(vaule) {
// 调用接口获取日志数据
}
throttle(fetchLogData, n);
Как показано на рисунке, бегущий журнал вытягивается через равные промежутки времени.
В приведенном выше примере показана частота срабатывания события управления дросселированием, и в то же время ограничиваются верхний и нижний пределы выполнения события, то есть событие запускается каждые n секунд для выполнения. Такой же сценарий может бытьscroll
mousemove
такие как более часто инициируемые события, расчет положения индикатора выполнения браузера,input
динамический поиск и т.д.
Анализ исходного кода Lodash для предотвращения дрожания
Основные сценарии реализации и применения дросселирования с защитой от сотрясений представлены выше, что нам удобно для понимания и использования. В реальных бизнес-сценариях мы будем выбирать более зрелые сторонние библиотеки для достижения эффекта анти-тряски и дросселирования. В настоящее время широко используютсяLodash
,Underscore.js
Подождите, давайте проанализируемLodash
Реализован предусмотренный метод троттлинга против встряски.
Защита от сотрясений: основная идея защиты от сотрясений Lodash заключается не в частом управлении таймером, а в достиженииshouldInvoke
принять решение о реализацииfunc
функция, предоставляемая только извнеcancel
Таймер отменяется только тогда, когда метод отменяет задержку.
Следующие детали представлены в модуле выполнения функций.shouldInvoke
Внутренняя логика реализации, вызываемая в переключателе таймера и функции входа, чтобы решить, следует ли ее выполнять.func
функция. Для конкретного контента мы можем увидеть некоторые комментарии к исходному коду ниже. Разбираем его на четыре модуля для анализа: базовое определение, переключение таймера, выполнение функции и внешний обратный вызов.
Основное определение (включая общую структуру)
Ниже приведеныLodash
Для реализации общей структуры кода защиты от сотрясений функция входа определяет некоторые переменные, связанные с таймером и выполнением функции. Всего 10 переменных, из которыхmaxWait、timerId、lastCallTime、lastInvokeTime、leading、maxing、trailing
Семь переменных, связанных со временем, являются важной поддержкой для реализации переключателей таймера и модулей выполнения функций.
import isObject from './isObject.js'
import root from './.internal/root.js'
function debounce(func, wait, options) {
/** ====== 基础定义 ====== */
let lastArgs, // 上一次执行 debounced 的 arguments
lastThis, // 上一次的 this
maxWait, // 最大等待时间,保证大于设置的最大间隔后一定会执行,用于实现节流效果
result, // 函数 func 执行后的返回值
timerId, // 定时器 ID
lastCallTime // 上一次调用 debounce 的时间
let lastInvokeTime = 0 // 上一次执行 func 的时间,用于实现节流效果
let leading = false // 延迟前即第一次触发
let maxing = false // 是否设置了最大等待时间 maxWait,多用于实现节流效果
let trailing = true // 延迟后即最后一次触发
// Bypass `requestAnimationFrame` by explicitly setting `wait=0`.
const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function')
if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
// 隐式转换
wait = +wait || 0
/**
* isObject 判断是否是一个对象
* function isObject(value) {
* const type = typeof value
* return value != null && (type == 'object' || type == 'function')
* }
*/
if (isObject(options)) {
leading = !!options.leading
maxing = 'maxWait' in options
// maxWait 取 maxWait 和 wait 中最大值,为实现节流效果,需保证 maxWait 的实际值大于 wait
maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
trailing = 'trailing' in options ? !!options.trailing : trailing
}
/** ====== 定时器开关 ====== */
// 设置定时器
function startTimer(pendingFunc, wait) {}
// 取消定时器
function cancelTimer(id) {}
// 计算仍需等待的时间
function remainingWait(time) {}
// 定时器回调
function timerExpired() {}
/** ====== 函数执行 ====== */
// 延迟前
function leadingEdge(time) {}
// 延迟后回调
function trailingEdge(time) {}
// 执行 func 函数
function invokeFunc(time) {}
// 判断此时是否应该执行 func 函数
function shouldInvoke(time) {}
/** ====== 对外回调 ====== */
// 取消延迟
function cancel() {}
// 立即调用
function flush() {}
// 判断是否在定时中
function pending() {}
// 入口函数
function debounced(...args) {}
debounced.cancel = cancel
debounced.flush = flush
debounced.pending = pending
return debounced
}
export default debounce
переключатель таймера
/** ====== 定时器开关 ====== */
// 设置定时器
function startTimer(pendingFunc, wait) {
if (useRAF) {
// 没设置 wait 或设置 wait 为 0 时调用 window.requestAnimationFrame()。
// 要求浏览器在下次重绘之前调用指定的回调函数更新动画
root.cancelAnimationFrame(timerId)
return root.requestAnimationFrame(pendingFunc)
}
return setTimeout(pendingFunc, wait)
}
// 取消定时器
function cancelTimer(id) {
if (useRAF) {
return root.cancelAnimationFrame(id)
}
clearTimeout(id)
}
// 计算仍需等待的时间
function remainingWait(time) {
// 当前时间与上一次调用 debounce 的间隔
const timeSinceLastCall = time - lastCallTime
// 当前时间与上一次执行 func 的间隔
const timeSinceLastInvoke = time - lastInvokeTime
// 剩余等待时间
const timeWaiting = wait - timeSinceLastCall
// 是否设置了最大等待时间 ( 是否设置为节流 )
// 否:剩余等待时间
// 是:剩余等待时间 和 当前时间与上一次执行 func 的间隔 中的最小值
return maxing
? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
: timeWaiting
}
// 定时器回调
function timerExpired() {
const time = Date.now()
// 应该执行 func 函数时,执行延迟后回调
if (shouldInvoke(time)) {
return trailingEdge(time)
}
// 计算仍需等待的时间,重置定时器
timerId = startTimer(timerExpired, remainingWait(time))
}
выполнение функции
/** ====== 函数执行 ====== */
// 延迟前
function leadingEdge(time) {
// 设置上次执行 func 函数的时间
lastInvokeTime = time
// 设置定时器
timerId = startTimer(timerExpired, wait)
// 如果设置了 leading 则立即执行 func 函数一次
return leading ? invokeFunc(time) : result
}
// 延迟后回调
function trailingEdge(time) {
timerId = undefined
// trailing 延迟后继续触发一次
// lastArgs 标记着 debounce 至少执行过一次
if (trailing && lastArgs) {
return invokeFunc(time)
}
// 重置参数
lastArgs = lastThis = undefined
return result
}
// 执行 func 函数
function invokeFunc(time) {
const args = lastArgs
const thisArg = lastThis
lastArgs = lastThis = undefined
lastInvokeTime = time
result = func.apply(thisArg, args)
return result
}
// 判断此时是否应该执行 func 函数
function shouldInvoke(time) {
// 当前时间与上一次调用 debounce 的间隔
const timeSinceLastCall = time - lastCallTime
// 当前时间与上一次执行 func 的间隔
const timeSinceLastInvoke = time - lastInvokeTime
// 首次调用
// 超出等待时间间隔 wait
// 系统时间发生了变更
// 超出最长等待时间 maxWait
return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
(timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait))
}
внешний обратный вызов
/** ====== 对外回调 ====== */
// 取消延迟
function cancel() {
// 取消定时器
if (timerId !== undefined) {
cancelTimer(timerId)
}
// 重置参数
lastInvokeTime = 0
lastArgs = lastCallTime = lastThis = timerId = undefined
}
// 立即调用
function flush() {
return timerId === undefined ? result : trailingEdge(Date.now())
}
// 判断是否在定时中
function pending() {
return timerId !== undefined
}
Throttling: Реализация функции дросселирования в Lodash проста, напрямую вызывая функцию защиты от сотрясений и устанавливая входной параметрmaxWait
добиться дросселирующего эффекта.
function throttle(func, wait, options) {
let leading = true
let trailing = true
if (typeof func !== 'function') {
throw new TypeError('Expected a function')
}
if (isObject(options)) {
leading = 'leading' in options ? !!options.leading : leading
trailing = 'trailing' in options ? !!options.trailing : trailing
}
return debounce(func, wait, {
leading,
trailing,
'maxWait': wait
})
}
export default throttle
выше правильноLodash
Краткий анализ реализации защиты от сотрясений и дросселирования.В реальных бизнес-сценариях предоставляемые им методы противодействия сотрясениям и дросселирования, как правило, можно использовать напрямую. Если вам нужны дополнительные настраиваемые функции, которые могут быть не реализованы или не поддерживают настройку, вы можете рассмотреть возможность реализации их самостоятельно на основе вашего понимания исходного кода для удовлетворения реальных потребностей бизнеса.
Разница между антишейком и троттлингом, какой из них следует использовать согласно реальному бизнес-сценарию
визуальное сравнение,Посмотреть онлайн(Примечание: взято изSitu Zhengmei — функция Anti-Shake и функция Throttling )
Anti-shake может использоваться для непредсказуемого активного поведения пользователя, например, когда пользователь вводит контент на сервер для динамических результатов поиска. Скорость набора текста пользователем непредсказуема и нерегулярна.
Дросселирование может использоваться для некоторых действий, не связанных с активностью пользователя, или предсказуемых действий пользователя. Например, когда пользователь перемещает окно продукта, чтобы отправить запрос о скрытых точках, скользящая фиксированная высота является известной логикой и закономерностью.
Суммировать
Использование идеи защиты от сотрясения и дросселирования для контроля времени выполнения функций может сохранить производительность и избежать негативного взаимодействия с пользователем, вызванного зависанием страницы. Понятия anti-shake и throttling схожи и их трудно различить.Вышеупомянутое содержание статьи было представлено с точки зрения собственного понимания автором anti-shake и throttling.
Новички могут увидеть другое понимание и введение в «Расширенном программировании на JavaScript» или других технических статьях автора и могут увидеть, что дроссель в «Расширенном программировании на JavaScript» на самом деле устраняет дребезг, и следует использовать динамический поиск для предотвращения дизеринга и в реальном времени. поиск должен использовать различные точки зрения и аргументы, такие как дросселирование. Я надеюсь, что после того, как вы поймете, что такое защита от сотрясения и дросселирования, вы сможете решить использовать антивибрацию и дросселирование в соответствии с реальным сценарием приложения и запросить детали, а также выбрать более разумный и подходящий метод.
использованная литература
Situ Zhengmei — функция Anti-Shake и функция Throttling
Понимание исходного кода дроссельной заслонки lodash
Какова функция защиты от сотрясений и дросселирования? ? ?
Темы изучения JavaScript, за которыми следует подчеркивание стабилизации изображения
Тема JavaScript: научитесь сокращать количество потоков с помощью подчеркивания
Как работает стабилизация и регулирование Lodash
Рекомендуемое чтение
Минимальные запасы для электронной коммерции — артикул и реализация алгоритма
Что нужно знать об управлении проектами
Как построить глобальную систему поиска кода от 0 до 1
Как создать платформу сборки и развертывания, подходящую для вашей команды
работы с открытым исходным кодом
- Zhengcaiyun интерфейсный таблоид
адрес с открытым исходным кодомwww.zoo.team/openweekly/(На главной странице официального сайта таблоида есть группа обмена WeChat)
- skuDemo
адрес с открытым исходным кодомGitHub.com/Chinese Patent Medicine-Inc/Reservoir…
Карьера
ZooTeam, молодая, увлеченная и творческая команда, связанная с отделом исследований и разработок продукции Zhengcaiyun, базируется в живописном Ханчжоу. В настоящее время в команде более 50 фронтенд-партнеров, средний возраст которых составляет 27 лет, и почти 30% из них — инженеры с полным стеком, настоящая молодежная штурмовая группа. В состав членов входят «ветераны» солдат из Ali и NetEase, а также первокурсники из Чжэцзянского университета, Университета науки и технологий Китая, Университета Хандянь и других школ. В дополнение к ежедневным деловым связям, команда также проводит технические исследования и фактические боевые действия в области системы материалов, инженерной платформы, строительной платформы, производительности, облачных приложений, анализа и визуализации данных, а также продвигает и внедряет ряд внутренних технологий. Откройте для себя новые горизонты передовых технологических систем.
Если вы хотите измениться, вас забрасывают вещами, и вы надеетесь начать их бросать; если вы хотите измениться, вам сказали, что вам нужно больше идей, но вы не можете сломать игру; если вы хотите изменить , у вас есть возможность добиться этого результата, но вы не нужны; если вы хотите изменить то, чего хотите достичь, вам нужна команда для поддержки, но вам некуда вести людей; если вы хотите изменить установившийся ритм, это будет "5 лет рабочего времени и 3 года стажа работы"; если вы хотите изменить исходный Понимание хорошее, но всегда есть размытие того слоя оконной бумаги.. , Если вы верите в силу веры, верьте, что обычные люди могут достичь необыкновенных вещей, и верьте, что они могут встретить лучшего себя. Если вы хотите участвовать в процессе становления бизнеса и лично способствовать росту фронтенд-команды с глубоким пониманием бизнеса, надежной технической системой, технологиями, создающими ценность, и побочным влиянием, я думаю, что мы должны говорить. В любое время, ожидая, пока вы что-нибудь напишете, отправьте это наZooTeam@cai-inc.com