一、函数为什么要防抖

有如下代码

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为返回函数调用时传入的参数,传给methodlet debounced = function(...args) {return new Promise (resolve => {// 用于记录原函数执行结果let result// 将method执行时this的指向设为debounce返回的函数被调用时的this指向let context = this// 如果存在定时器则将其清除if (timeout) {clearTimeout(timeout)}// 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为nullif (immediate) {// 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null// 这样确保立即执行后wait毫秒内不会被再次触发let callNow = !timeouttimeout = 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 valtry {val = await debouncedFn(4)} catch (err) {console.error(err)}// 停止缩放1S后输出:// 原函数的返回值为:16console.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 timeoutreturn function() {if (timeout) {clearTimeout(timeout)}timeout = setTimeout(() => {method.call(context)}, wait)}
}
复制代码复制代码

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

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

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

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

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

function debounce(method, wait) {let timeout// args为返回函数调用时传入的参数,传给methodreturn function(...args) {let context = thisif (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 timeoutreturn function(...args) {let context = thisif (timeout) {clearTimeout(timeout)}// 立即执行需要两个条件,一是immediate为true,二是timeout未被赋值或被置为nullif (immediate) {// 如果定时器不存在,则立即执行,并设置一个定时器,wait毫秒后将定时器置为null// 这样确保立即执行后wait毫秒内不会被再次触发let callNow = !timeouttimeout = 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 = thisif (timeout) {clearTimeout(timeout)}if (immediate) {let callNow = !timeouttimeout = 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, resultlet debounced = function(...args) {let context = thisif (timeout) {clearTimeout(timeout)}if (immediate) {let callNow = !timeouttimeout = 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, resultlet debounced = function(...args) {// 返回一个Promise,以便可以使用then或者Async/Await语法拿到原函数返回值return new Promise(resolve => {let context = thisif (timeout) {clearTimeout(timeout)}if (immediate) {let callNow = !timeouttimeout = setTimeout(() => {timeout = null}, wait)if (callNow) {result = method.apply(context, args)// 将原函数的返回值传给resolveresolve(result)}} else {timeout = setTimeout(() => {result = method.apply(context, args)// 将原函数的返回值传给resolveresolve(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 valtry {val = await debouncedFn(4)} catch (err) {console.error(err)}console.log(`原函数返回值为${val}`)
}, false)// 停止缩放1S后输出:
// 原函数的返回值为:16复制代码

javascript函数防抖Debounce相关推荐

  1. SAP UI5和Angular的函数防抖(Debounce)和函数节流(Throttle)实现原理介绍

    这是Jerry 2021年的第 11 篇文章,也是汪子熙公众号总共第 282 篇原创文章. Jerry之前的文章 SAP UI5 OData谣言粉碎机:极短时间内发送两个Odata request, ...

  2. 函数防抖(debounce)和节流(throttle)在H5编辑器项目中的应用

    函数防抖(debounce)和节流(throttle)在H5编辑器项目中的应用 文章目录 函数防抖(debounce)和节流(throttle)在H5编辑器项目中的应用 1. 为什么要防抖节流 1.1 ...

  3. 函数防抖Debounce和函数节流Throttle

    函数节流 & 函数防抖 函数节流和函数防抖 函数节流和函数防抖二者很容易被混淆起来.下面贴英文原文,建议认真阅读: Debouncing enforces that a function no ...

  4. 函数防抖(debounce)和节流(throttle)以及lodash的debounce源码赏析

    函数节流和去抖的出现场景,一般都伴随着客户端 DOM 的事件监听.比如scroll resize等事件,这些事件在某些场景触发非常频繁. 比如,实现一个原生的拖拽功能(不能用 H5 Drag& ...

  5. 函数防抖debounce和节流throttle

    debounce 函数防抖 1.什么是函数防抖 函数防抖是优化高频率执行 js 代码的一种手段 可以让被调用的函数在一次连续的高频率操作过程中只被调用一次 2.函数防抖的作用 减少代码执行次数,提升网 ...

  6. JavaScript函数防抖与截流

    一.在前端开发当中,有些交互事件,会频繁触发.这样会导致我们的页面渲染性能,如果频繁触发接口调用的话,会直接导致服务器性能的浪费.      例如:键盘事件 keyup作为测试 <ul>& ...

  7. JavaScript函数节流和函数防抖

    1.为什么需要函数防抖和函数节流? 在浏览器中某些计算和处理要比其他的昂贵很多.例如DOM操作比起非DOM交互需要更多的内存和CPU占用时间.连续尝试进行过多的DOM操作可能会导致浏览器挂起,甚至崩溃 ...

  8. JavaScript 函数循环、延时、节流、防抖

    函数循环(setInterval) 间隔指定的毫秒数不停地执行指定的代码 <button onclick="myStartFunction()">开始</butt ...

  9. ege限制鼠标移动的函数_浅谈函数节流和函数防抖

    什么是函数节流和函数防抖?下面本篇文章就来给大家浅谈一下函数节流和函数防抖.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. 前言 事件的触发权很多时候都属于用户,有些情况下会产生问题 ...

  10. 函数防抖 和 函数节流

    函数防抖 和 函数节流 函数防抖(debounce) 概念 函数防抖 就是指 触发事件后 在 n 秒内 函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间. 简单的说,当一个动作 ...

最新文章

  1. RMAN-03002、RMAN-06059
  2. matlab m n size a,matlab—size用法总结
  3. explicit_defaults_for_timestamp参数的专题报告
  4. OpenCV凸凹面函数convexHull使用的实例(附完整代码)
  5. php mongodb _id,PHP库 查询Mongodb中的文档ID的方法
  6. vpr文件转换flac_关于便携播放器音频格式转换的问题
  7. 关于JAP FetchType.LAZY(hibernate实现)的理解
  8. 易语言php支付宝,支付宝填表登录易语言源码
  9. linux下mips裸机编译,裸机交叉编译器输入
  10. PHP trim()函数详解
  11. C语言学习笔记---结构体的定义和初始化
  12. Linux怎么取消软链接
  13. 计算机等级的判断,计算机等级考试一级WPS判断题及答案
  14. FPS游戏中的喷漆效果原理
  15. 应届生Java后台开发面试整理(含答案,不定期更新)
  16. win10--笔记本能连上手机热点,但不能连接WIFI
  17. 用Python画二元高次方程
  18. Windows 系统引导过程
  19. Guava 操作 集合
  20. 智能化API-发票查验接口篇

热门文章

  1. Atitit 编程范式之道 attilax著 v2 u66.docx Atitit 编程范式之道 attilax著 著 1. 编程范式与编程语言的关系是什么? 2 2. 高效率的编程范式 2
  2. Atitit 数据控制语言与权限  DCL
  3. Atitit 短信验证的漏洞 目录 1.1. APP读取短信 1 1.2. 手机上访问的业务来说,短信验证码就没那么独立了 1 1.3. 短信保管箱” 1 1.4. 自动把短信备份到云端的功能。 2
  4. atitit.重装系统需要备份的资料总结 o84..
  5. paip.AJAX回调函数不起作用的解决
  6. (转)证券投资及财富管理市场创新趋势
  7. (转)“宇宙之王”高盛在历史的交叉口,不得不全面走向机器自动化
  8. 阿里云混合云新一代运维平台的演进与实践
  9. 为了永不停机的计算服务 | 凌云时刻
  10. delphi 如何判断 socket 连接成功_Linux下的C++ socket编程实例