函数为什么要防抖

一、函数为什么要防抖

有如下代码

 

复制代码

window.onresize = () => { console.log('触发窗口监听回调函数') } 复制代码

当我们在PC上缩放浏览器窗口时,一秒可以轻松触发30次事件。手机端触发其他Dom时间监听回调时同理。

这里的回调函数只是打印字符串,如果回调函数更加复杂,可想而知浏览器的压力会非常大,用户体验会很糟糕。

resizescroll等Dom事件的监听回调会被频繁触发,因此我们要对其进行限制。

二、实现思路

函数去抖简单来说就是对于一定时间段的连续的函数调用,只让其执行一次,初步的实现思路如下:

第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。

三、Debounce 应用场景

  • 每次 resize/scroll 触发统计事件
  • 文本输入的验证(连续输入文字后发送 AJAX 请求进行验证,验证一次就好)

四、函数防抖最终版

代码说话,有错恳请指出

 

复制代码

function debounce(method, wait, immediate) { let timeout // debounced函数为返回值 // 使用Async/Await处理异步,如果函数异步执行,等待setTimeout执行完,拿到原函数返回值后将其返回 // args为返回函数调用时传入的参数,传给method let debounced = function(...args) { return new Promise (resolve => { // 用于记录原函数执行结果 let result // 将method执行时this的指向设为debounce返回的函数被调用时的this指向 let context = this // 如果存在定时器则将其清除 if (timeout) { clearTimeout(timeout) } // 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为null if (immediate) { // 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null // 这样确保立即执行后wait毫秒内不会被再次触发 let callNow = !timeout timeout = setTimeout(() => { timeout = null }, wait) // 如果满足上述两个条件,则立即执行并记录其执行结果 if (callNow) { result = method.apply(context, args) resolve(result) } } else { // 如果immediate为false,则等待函数执行并记录其执行结果 // 并将Promise状态置为fullfilled,以使函数继续执行 timeout = setTimeout(() => { // args是一个数组,所以使用fn.apply // 也可写作method.call(context, ...args) result = method.apply(context, args) resolve(result) }, wait) } }) } // 在返回的debounced函数上添加取消方法 debounced.cancel = function() { clearTimeout(timeout) timeout = null } return debounced } 复制代码

需要注意的是,如果需要原函数返回值,调用防抖后的函数的外层函数需要使用Async/Await语法等待执行结果返回

使用方法见代码:

 

复制代码

function square(num) { return Math.pow(num, 2) } let debouncedFn = debounce(square, 1000, false) window.addEventListener('resize', async () => { let val try { val = await debouncedFn(4) } catch (err) { console.error(err) } // 停止缩放1S后输出: // 原函数的返回值为:16 console.log(`原函数返回值为${val}`) }, false) 复制代码

具体的实现步骤请往下看

五、Debounce 的实现

1. 《JavaScript高级程序设计》(第三版)中的实现

 

复制代码

function debounce(method, context) { clearTimeout(method.tId) method.tId = setTimeout(() => { method.call(context) }, 1000) } function print() { console.log('Hello World') } window.onresize = debounce(print) 复制代码

我们不停缩放窗口,当停止1S后,打印出Hello World。

有个可以优化的地方: 此实现方法有副作用(Side Effect),改变了输入值(method),给method新增了属性

2. 优化第一版:消除副作用,将定时器隔离

 

复制代码

function debounce(method, wait, context) { let timeout return function() { if (timeout) { clearTimeout(timeout) } timeout = setTimeout(() => { method.call(context) }, wait) } } 复制代码

3. 优化第二版:自动调整this正确指向

之前的函数我们需要手动传入函数执行上下文context,现在优化将 this 指向正确的对象。

 

复制代码

function debounce(method, wait) { let timeout return function() { // 将method执行时this的指向设为debounce返回的函数被调用时的this指向 let context = this if (timeout) { clearTimeout(timeout) } timeout = setTimeout(() => { method.call(context) }, wait) } } 复制代码

4. 优化第三版:函数可传入参数

即便我们的函数不需要传参,但是别忘了JavaScript 在事件处理函数中会提供事件对象 event,所以我们要实现传参功能。

 

复制代码

function debounce(method, wait) { let timeout // args为返回函数调用时传入的参数,传给method return function(...args) { let context = this if (timeout) { clearTimeout(timeout) } timeout = setTimeout(() => { // args是一个数组,所以使用fn.apply // 也可写作method.call(context, ...args) method.apply(context, args) }, wait) } } 复制代码

5. 优化第四版:提供立即执行选项

有些时候我不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发n毫秒后,才可以重新触发执行。

 

复制代码

function debounce(method, wait, immediate) { let timeout return function(...args) { let context = this if (timeout) { clearTimeout(timeout) } // 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为null if (immediate) { // 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null // 这样确保立即执行后wait毫秒内不会被再次触发 let callNow = !timeout timeout = setTimeout(() => { timeout = null }, wait) if (callNow) { method.apply(context, args) } } else { // 如果immediate为false,则函数wait毫秒后执行 timeout = setTimeout(() => { // args是一个类数组对象,所以使用fn.apply // 也可写作method.call(context, ...args) method.apply(context, args) }, wait) } } } 复制代码

6. 优化第五版:提供取消功能

有些时候我们需要在不可触发的这段时间内能够手动取消防抖,代码实现如下:

 

复制代码

function debounce(method, wait, immediate) { let timeout // 将返回的匿名函数赋值给debounced,以便在其上添加取消方法 let debounced = function(...args) { let context = this if (timeout) { clearTimeout(timeout) } if (immediate) { let callNow = !timeout timeout = setTimeout(() => { timeout = null }, wait) if (callNow) { method.apply(context, args) } } else { timeout = setTimeout(() => { method.apply(context, args) }, wait) } } // 加入取消功能,使用方法如下 // let myFn = debounce(otherFn) // myFn.cancel() debounced.cancel = function() { clearTimeout(timeout) timeout = null } } 复制代码

至此,我们已经比较完整地实现了一个underscore中的debounce函数。

六、遗留问题

需要防抖的函数可能是存在返回值的,我们要对这种情况进行处理,underscore的处理方法是将函数返回值在返回的debounced函数内再次返回,但是这样其实是有问题的。如果参数immediate传入值不为true的话,当防抖后的函数第一次被触发时,如果原始函数有返回值,其实是拿不到返回值的,因为原函数是在setTimeout内,是异步延迟执行的,而return是同步执行的,所以返回值是undefined

第二次触发时拿到的返回值其实是第一次执行的返回值,第三次触发时拿到的返回值其实是第二次执行的返回值,以此类推。

1. 使用回调函数处理函数返回值

 

复制代码

function debounce(method, wait, immediate, callback) { let timeout, result let debounced = function(...args) { let context = this if (timeout) { clearTimeout(timeout) } if (immediate) { let callNow = !timeout timeout = setTimeout(() => { timeout = null }, wait) if (callNow) { result = method.apply(context, args) // 使用回调函数处理函数返回值 callback && callback(result) } } else { timeout = setTimeout(() => { result = method.apply(context, args) // 使用回调函数处理函数返回值 callback && callback(result) }, wait) } } debounced.cancel = function() { clearTimeout(timeout) timeout = null } return debounced } 复制代码

这样我们就可以在函数防抖时传入一个回调函数来处理函数的返回值,使用代码如下:

 

复制代码

function square(num) { return Math.pow(num, 2) } let debouncedFn = debounce(square, 1000, false, val => { console.log(`原函数的返回值为:${val}`) }) window.addEventListener('resize', () => { debouncedFn(4) }, false) // 停止缩放1S后输出: // 原函数的返回值为:16 复制代码

2. 使用Promise处理返回值

 

复制代码

function debounce(method, wait, immediate) { let timeout, result let debounced = function(...args) { // 返回一个Promise,以便可以使用then或者Async/Await语法拿到原函数返回值 return new Promise(resolve => { let context = this if (timeout) { clearTimeout(timeout) } if (immediate) { let callNow = !timeout timeout = setTimeout(() => { timeout = null }, wait) if (callNow) { result = method.apply(context, args) // 将原函数的返回值传给resolve resolve(result) } } else { timeout = setTimeout(() => { result = method.apply(context, args) // 将原函数的返回值传给resolve resolve(result) }, wait) } }) } debounced.cancel = function() { clearTimeout(timeout) timeout = null } return debounced } 复制代码

使用方法一:在调用防抖后的函数时,使用then拿到原函数的返回值

 

复制代码

function square(num) { return Math.pow(num, 2) } let debouncedFn = debounce(square, 1000, false) window.addEventListener('resize', () => { debouncedFn(4).then(val => { console.log(`原函数的返回值为:${val}`) }) }, false) // 停止缩放1S后输出: // 原函数的返回值为:16 复制代码

使用方法二:调用防抖后的函数的外层函数使用Async/Await语法等待执行结果返回

使用方法见代码:

 

复制代码

function square(num) { return Math.pow(num, 2) } let debouncedFn = debounce(square, 1000, false) window.addEventListener('resize', async () => { let val try { val = await debouncedFn(4) } catch (err) { console.error(err) } console.log(`原函数返回值为${val}`) }, false) // 停止缩放1S后输出: // 原函数的返回值为:16

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/4083.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

kali中pwn环境的配置-一步到位

文章目录 解决kali没有网络的问题(配置eth0即可解决)换源前提工具更新安装pwntools安装vscode安装pycharm安装中文输入法gdb配置ROPgadgetone_gadgetLibcSearcherchecksec待完善捏……………… 为了防止有时候出现某些问题需要重新配置pwn环境&#xff0…

Unity打包PC端exe,压缩打包为一个exe文件

目录 一.打包成功 1.打包输出文件 二.压缩输出目录为exe单个文件 1.添加到压缩文件 2.其他设置 1.点击“高级→自压缩选项” 2.修改解压后运行程序 3.设置模式 4.更新 三、生成.exe 一.打包成功 1.打包输出文件 1、一个后缀为 BurstDebugInformation_DoNotShip的文…

Swift加载Lottie

OC使用时&#xff0c;需要通过swift透出方法供OC使用 // 此处文件名可以从Build Settings下搜索Generated Header Name的值得出 #import <Test-Swift.h>一、导入包 target iOS douse_frameworks!# 此处pod lottie-ios end二、功能实现 1. 创建组件 import LottieobjcM…

Android 12 Starting window的添加与移除

添加&#xff1a; 04-13 16:29:55.931 2944 7259 D jinyanmeistart: at com.android.server.wm.StartingSurfaceController.createSplashScreenStartingSurface(StartingSurfaceController.java:87) 04-13 16:29:55.931 2944 7259 D jinyanmeistart: at com.android.server.wm.…

ios不兼容Svg Wave的动画的解决方法

近日也是用上了SvgWave&#xff0c;十分的好看 Svg Wave - A free & beautiful gradient SVG wave Generator. 大家感兴趣的也可以了解一下 【场景】 使用SvgWave的Animate&#xff0c;并生成svg代码使用&#xff0c;windows web端、朋友的安卓移动端都能够正常执行动画…

P2DR

P2DR 是一种网络安全模型&#xff0c;代表“Policy&#xff08;策略&#xff09;- Protection&#xff08;防护&#xff09;- Detection&#xff08;检测&#xff09;- Response&#xff08;响应&#xff09;”。这个模型由信息安全专家Wheeler在1990年代提出&#xff0c;用于指…

前端CSS基础10(浮动)

前端CSS基础10&#xff08;浮动&#xff09; 浮动元素浮动后的特点浮动后的特点浮动后的影响及解决 浮动布局小练习 浮动 CSS中的浮动是一种布局技术&#xff0c;常用于实现元素的排列和定位。通过使用float属性&#xff0c;可以让元素在页面中左浮动或右浮动&#xff0c;使得…

在PostgreSQL中如何有效地批量导入大量数据,并确保数据加载过程中的性能和稳定性?

文章目录 解决方案1. 使用COPY命令2. 调整配置参数3. 禁用索引和约束4. 使用事务5. 并发导入 总结 在PostgreSQL中&#xff0c;批量导入大量数据是一个常见的需求&#xff0c;特别是在数据迁移、数据仓库填充或大数据分析等场景中。为了确保数据加载过程中的性能和稳定性&#…

Compose和Android View相互使用

文章目录 Compose和Android View相互使用在Compose中使用View概述简单控件复杂控件嵌入XML布局 在View中使用Compose概述在Activity中使用Compose在Fragment中使用Compose布局使用多个ComposeView 在布局中使用Compose 组合使用 Compose和Android View相互使用 在Compose中使用…

AIGC - SD(中英文本生成图片) + PaddleHub/HuggingFace + stable-diffusion-webui

功能 stable-diffusion(文本生成图片)webui-win搭建&#xff08;开启api界面汉化&#xff09;PaddleHubHuggingFace: SD2&#xff0c;中文-alibaba/EasyNLP stable-diffusion-webui 下载与安装 环境相关下载 python&#xff08;文档推荐&#xff1a;Install Python 3.10.6 …

linux开发板开机启动向日葵

硬件&#xff1a;orangepi 5 pro 操作系统&#xff1a;ubuntu 20.4 lts 安装向日葵 根据我的实测&#xff0c;arm架构的ubuntu系统只能安装向日葵提供的麒麟系统的那个版本&#xff0c;具体安装方式官网下载页面有 允许任意用户连接到 X11 使用root用户登录后打开终端输入一下…

Windows python3.10安装psbody

Windows环境下安装psbody 下载mesh GitHub - MPI-IS/mesh: MPI-IS Mesh Processing Library 下载安装boost Boost Downloads 解压到 D:\software\boost_1_85_0&#xff0c;这个路径后面要设置为环境变量 BOOST_ROOT 的值。 添加 BOOST_ROOT 环境变量 您可以通过图形界面配…

php的curl请求,包含了post,get,put,delete

php的curl请求,包含了post,get,put,delete 这段代码是一个非常实用的HTTP通信工具&#xff0c;可以轻松集成到需要网络通信的PHP项目中。它提供了足够的灵活性&#xff0c;适合多种网络请求任务。 if (!function_exists("http_curl")) {/*** 发送HTTP请求* param s…

react函数组件传值(父子/子父/兄弟)

父子组件传值 子父组件传值 兄弟组件传值 注&#xff1a;本人前端小白 &#xff0c;如有不对的地方还请多多指教

knife4j swagger 使用笔记

1.接口访问的端口跟后台设置的不一致&#xff0c;接口请求无反应 处理办法 2.响应参数不显示问题 &#xff08;1&#xff09;返回的参数里面一定要有响应的参数对象&#xff0c;如下&#xff1a; &#xff08;2&#xff09;TableDataInfo 定义成泛型类 TableDataInfo package…

ros2 node 之间的通信方式之 —— Topic通信案例

文章目录 ros2 node 之间的通信方式之 Topic通信Topic 通信案例1、创建工作空间2、创建功能包3、编写发布者和订阅者代码3.1 topic_helloworld_pub.cpp3.2 topic_helloworld_sub.cpp 4、编写CMakeLists.txt5、编译工作空间下的功能包6、运行结果 ros2 node 之间的通信方式之 To…

AutoGPT-Forge使用教程,自行构建agent智能体

本博客给出AutoGPT-forge四个教程的翻译与理解&#xff0c;使用GPT4翻译&#xff0c; 参考官方教程https://aiedge.medium.com/autogpt-forge-a-comprehensive-guide-to-your-first-steps-a1dfdf46e3b4 使用AutoGPT Github代码日期2024/4/22&#xff1b; 博客开始编辑日期20…

C语言项目实战——扫雷

目录 1.前言 2.完整流程 2.1规划书 2.2代码部分 2.2.1文件的结构设计 2.2.2变量的创建 2.2.3菜单的基本实现 2.2.4初始化期棋盘 2.2.5输出完整棋盘 2.2.6埋雷的实现 2.2.7查询周围雷的数量 2.2.8扫雷的实现 2.2.9完整代码 3.总结 1.前言 哈喽大家好吖&#xff0c;今…

深入了解数据结构中的查找算法

目录 前言 1. 查找的基本概念 2. 顺序查找和折半查找 2.1 顺序查找 2.2 折半查找 2.3分块查找 3. 树形查找 3.1 二叉搜索树 (BST) 3.2平衡二叉树 3.3红黑树 4. B 树和 B 树 4.1 B 树 4.2 B 树 5. 哈希表 (Hash Table) 5.1 基本操作 5.2 实现 5.3 复杂性分析 5…

# IntelliJ IDEA 中 springboot 启动类 SpringApplication.run 报红分析

IntelliJ IDEA 中 springboot 启动类 SpringApplication.run 报红分析 一、原因分析&#xff1a;通常 SpringApplication.run 报红&#xff0c;可能是由以下几种原因造成的&#xff1a; 1、项目的主配置文件中 pom.xml 可能没有导入相关依赖。 2、方法参数错误&#xff1a;S…