热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

js基础中的防抖与节流

昨天被问到这个问题,发现自己还不是很清楚。找了很多博客,学习一下。防抖和节流是属于浏览器性能优化部分,事件的频繁触发,频繁操作dom,会导致页面卡顿和浏览器崩溃。但是,dom绑定的

昨天被问到这个问题,发现自己还不是很清楚。找了很多博客,学习一下。

防抖和节流是属于浏览器性能优化部分,事件的频繁触发,频繁操作dom,会导致页面卡顿和浏览器崩溃。但是,dom绑定的某些事件我们是控制不了它的触发频率




  1. window 的 resize()、scroll()

  2. mousemove

  3. mousedown、keydown

  4. 文字输入、自动完成的keyup事件,用户输入过程中事件不断的触发,会导致大量请求发出,但是响应跟不上。

对于window.resize()的处理,停止改变n毫秒后执行后续的处理,防抖 debounce

其他事件的需求,以一定频率执行后续的处理,期间不执行,节流 throttle

防抖 debounce

定义 : 策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。

简单实现的例子,滚动条监听

function debounce(fn, delay) {
let timer = null; // 借助闭包
console.log("借助闭包来实现")
return function () {
if (timer) {
// 正在计时的过程中,又触发了相同的事件,所以要取消当前计时,重新开始
clearTimeout(timer);
}
// 没有计时的存在,那就重新开始一个计时
timer = setTimeout(fn, delay);
}
}
function showTop() {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
console.log("获取滚动位置", scrollTop);
}
// 实现 停止滚动1s后才能再次打印出位置
window.Onscroll= debounce(showTop, 1000);

lodash 中的 debounce 函数:  _.debounce(func, [wait=0], [optiOns={}])

 再贴一个lodash中的源码,有时间看看

function debounce(func, wait, options) {
var nativeMax = Math.max,
toNumber,
nativeMin
var lastArgs,
lastThis,
maxWait,
result,
timerId,
lastCallTime,
// func 上一次执行的时间
lastInvokeTime = 0,
leading = false,
maxing = false,
trailing = true;
// func必须是函数
if (typeof func != 'function') {
throw new TypeError(FUNC_ERROR_TEXT);
}
// 对间隔时间的处理
wait = toNumber(wait) || 0;
// 对options中传入参数的处理
if (isObject(options)) {
leading = !!options.leading;
maxing = 'maxWait' in options;
maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
trailing = 'trailing' in options ? !!options.trailing : trailing;
}
// 执行要被触发的函数
function invokeFunc(time) {
var args = lastArgs,
thisArg = lastThis;
lastArgs = lastThis = undefined;
lastInvokeTime = time;
result = func.apply(thisArg, args);
return result;
}
// 在leading edge阶段执行函数
function leadingEdge(time) {
// Reset any `maxWait` timer.
lastInvokeTime = time;
// 为 trailing edge 触发函数调用设定定时器
timerId = setTimeout(timerExpired, wait);
// leading = true 执行函数
return leading ? invokeFunc(time) : result;
}
// 剩余时间
function remainingWait(time) {
// 距离上次debounced函数被调用的时间
var timeSinceLastCall = time - lastCallTime,
// 距离上次函数被执行的时间
timeSinceLastInvoke = time - lastInvokeTime,
// 用 wait 减去 timeSinceLastCall 计算出下一次trailing的位置
result = wait - timeSinceLastCall;
// 两种情况
// 有maxing: 比较出下一次maxing和下一次trailing的最小值,作为下一次函数要执行的时间
// 无maxing: 在下一次trailing时执行timerExpired
return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
}
// 根据时间判断 func 能否被执行
function shouldInvoke(time) {
var timeSinceLastCall = time - lastCallTime,
timeSinceLastInvoke = time - lastInvokeTime;
// 几种满足条件的情况
return (lastCallTime === undefined // 首次执行
|| (timeSinceLastCall >= wait) // 距离上次被调用已经超过 wait
|| (timeSinceLastCall <0)// 系统时间倒退
|| (maxing && timeSinceLastInvoke >= maxWait)); //超过最大等待时间
}
// 在 trailing edge 且时间符合条件时,调用 trailingEdge函数,否则重启定时器
function timerExpired() {
var time = now();
if (shouldInvoke(time)) {
return trailingEdge(time);
}
// 重启定时器
timerId = setTimeout(timerExpired, remainingWait(time));
}
// 在trailing edge阶段执行函数
function trailingEdge(time) {
timerId = undefined;
// 有lastArgs才执行,
// 意味着只有 func 已经被 debounced 过一次以后才会在 trailing edge 执行
if (trailing && lastArgs) {
return invokeFunc(time);
}
// 每次 trailingEdge 都会清除 lastArgs 和 lastThis,目的是避免最后一次函数被执行了两次
// 举个例子:最后一次函数执行的时候,可能恰巧是前一次的 trailing edge,函数被调用,而这个函数又需要在自己时延的 trailing edge 触发,导致触发多次
lastArgs = lastThis = undefined;
return result;
}
// cancel方法
function cancel() {
if (timerId !== undefined) {
clearTimeout(timerId);
}
lastInvokeTime = 0;
lastArgs = lastCallTime = lastThis = timerId = undefined;
}
// flush方法--立即调用
function flush() {
return timerId === undefined ? result : trailingEdge(now());
}
function debounced() {
var time = now(),
//是否满足时间条件
isInvoking = shouldInvoke(time);
lastArgs = arguments;
lastThis = this;
lastCallTime = time; //函数被调用的时间
// 无timerId的情况有两种:
// 1.首次调用
// 2.trailingEdge执行过函数
if (isInvoking) {
if (timerId === undefined) {
return leadingEdge(lastCallTime);
}
if (maxing) {
// Handle invocations in a tight loop.
timerId = setTimeout(timerExpired, wait);
return invokeFunc(lastCallTime);
}
}
// 负责一种case:trailing 为 true 的情况下,在前一个 wait 的 trailingEdge 已经执行了函数;
// 而这次函数被调用时 shouldInvoke 不满足条件,因此要设置定时器,在本次的 trailingEdge 保证函数被执行
if (timerId === undefined) {
timerId = setTimeout(timerExpired, wait);
}
return result;
}
debounced.cancel = cancel;
debounced.flush = flush;
return debounced;
}

节流 throttle

定义 :  固定周期内,只允许执行一次,期间如再次触发,不执行。周期结束后,又有事件触发,开始新的周期。

例子 : 还是监听滚动条,如果用户一直拖着滚动条移动,使用debounce防抖不停止就不会打印信息。但是,用户想在这种情况下,也能够每个一段时间触发一下。

function throttle(fn, delay) {
let valid = true; // 这是一个状态位
return function () {
if (!valid) {
// 正在一个执行周期内,不允许再次执行
return false;
}
// 可以执行,并开始新的周期
valid = false;
setTimeout(() => {
fn(),
valid = true; // 新周期开始标志
}, delay)
}
}
function showTop() {
let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
console.log("获取滚动位置", scrollTop);
}
// 实现 停止滚动1s后才能再次打印出位置
window.Onscroll= throttle(showTop, 1000);

lodash 中提供的 throttle方法:  _.throttle(func, [wait=0], [optiOns={}])

应用场景



  1. 搜索框的input事件,需要支持输入的实时搜索,可以使用节流,设置每隔一段时间后,必须查询相关内容

  2. resize() 事件,常见于页面适配时,一般使用防抖,因为只需要处理最后一次的变化。


注意



  • debounce 返回的函数必须是立即执行函数

function test() {
console.log(123)
}
setInterval(function () {
_.debounce(test, 1500)
}, 500)
// 每次setInterval执行后,返回函数没有执行
// 点击事件 这样写也不会生效
btn.addEventListener('click', function () {
_.debounce(test, 1500)
})
// 正确做法
btn.addEventListener('click', test)
setInterval(_.debounce(test, 1500), 500)

参考文章:

https://segmentfault.com/a/1190000018428170

https://blog.csdn.net/hupian1989/article/details/80920324

https://blog.csdn.net/duola8789/article/details/78871789


推荐阅读
  • 本文总结了在编写JS代码时,不同浏览器间的兼容性差异,并提供了相应的解决方法。其中包括阻止默认事件的代码示例和猎取兄弟节点的函数。这些方法可以帮助开发者在不同浏览器上实现一致的功能。 ... [详细]
  • 如何优化Webpack打包后的代码分割
    本文介绍了如何通过优化Webpack的代码分割来减小打包后的文件大小。主要包括拆分业务逻辑代码和引入第三方包的代码、配置Webpack插件、异步代码的处理、代码分割重命名、配置vendors和cacheGroups等方面的内容。通过合理配置和优化,可以有效减小打包后的文件大小,提高应用的加载速度。 ... [详细]
  • python+selenium十:基于原生selenium的二次封装fromseleniumimportwebdriverfromselenium.webdriv ... [详细]
  • [翻译]PyCairo指南裁剪和masking
    裁剪和masking在PyCairo指南的这个部分,我么将讨论裁剪和masking操作。裁剪裁剪就是将图形的绘制限定在一定的区域内。这样做有一些效率的因素࿰ ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • WPF开发心率检测大数据曲线图的高性能实现方法
    本文介绍了在WPF开发中实现心率检测大数据曲线图的高性能方法。作者尝试过使用Canvas和第三方开源库,但性能和功能都不理想。最终作者选择使用DrawingVisual对象,并结合局部显示的方式实现了自己想要的效果。文章详细介绍了实现思路和具体代码,对于不熟悉DrawingVisual的读者可以去微软官网了解更多细节。 ... [详细]
  • 如何在HTML中获取鼠标的当前位置
    本文介绍了在HTML中获取鼠标当前位置的三种方法,分别是相对于屏幕的位置、相对于窗口的位置以及考虑了页面滚动因素的位置。通过这些方法可以准确获取鼠标的坐标信息。 ... [详细]
  • 使用eclipse创建一个Java项目的步骤
    本文介绍了使用eclipse创建一个Java项目的步骤,包括启动eclipse、选择New Project命令、在对话框中输入项目名称等。同时还介绍了Java Settings对话框中的一些选项,以及如何修改Java程序的输出目录。 ... [详细]
  • EPPlus绘制刻度线的方法及示例代码
    本文介绍了使用EPPlus绘制刻度线的方法,并提供了示例代码。通过ExcelPackage类和List对象,可以实现在Excel中绘制刻度线的功能。具体的方法和示例代码在文章中进行了详细的介绍和演示。 ... [详细]
  • 本文介绍了pack布局管理器在Perl/Tk中的使用方法及注意事项。通过调用pack()方法,可以控制部件在显示窗口中的位置和大小。同时,本文还提到了在使用pack布局管理器时,应注意将部件分组以便在水平和垂直方向上进行堆放。此外,还介绍了使用Frame部件或Toplevel部件来组织部件在窗口内的方法。最后,本文强调了在使用pack布局管理器时,应避免在中间切换到grid布局管理器,以免造成混乱。 ... [详细]
  • 微信小程序导航跟随的实现方法
    本文介绍了在微信小程序中实现导航跟随的方法。通过设置导航的position属性和绑定滚动事件,可以实现页面向下滚动到导航位置时,导航固定在页面最上方;页面向上滚动到导航位置时,导航恢复到原始位置;点击导航可以平滑跳转到相应位置。代码示例也给出了具体实现方法。 ... [详细]
  • 前段时间做一个项目,需求是对每个视频添加预览图,这个问题最终选择方案是:用canvas.toDataYRL();来做转换获取视频的一个截图,添加到页面中,达到自动添加预览图的目的。 ... [详细]
  • Tkinter Frame容器grid布局并使用Scrollbar滚动原理
    本文介绍了如何使用Tkinter实现Frame容器的grid布局,并通过Scrollbar实现滚动效果。通过将Canvas作为父容器,使用滚动Canvas来滚动Frame,实现了在Frame中添加多个按钮,并通过Scrollbar进行滚动。同时,还介绍了更新Frame大小和绑定滚动按钮的方法,以及配置Scrollbar的相关参数。 ... [详细]
  • html和js代码互转,html转html5
    本文目录一览:1、html网页跳转javascript代码实现 ... [详细]
author-avatar
手机用户2502883105
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有