函数防抖与节流是日常开发中经常用到的技巧,也是前端面试中的常客,但是发现自己工作一年多了,要么直接复用已有的代码或工具,要么抄袭《JS高级程序设计》书中所述“函数节流”,(实际上红宝书上的实现类似是函数防抖而不是函数节流),还没有认真的总结和亲自实现这两个方法,实在是一件蛮丢脸的事。网上关于这方面的资料简直就像是中国知网上的“水论文”,又多又杂,难觅精品,当然,本文也是一篇很水的文章,只当是个人理解顺便备忘,毕竟年纪大了,记忆力下降严重。CSS-Tricks上这篇文章Debouncing and Throttling Explained Through Examples算是非常通识的博文,值得一读。

函数防抖与节流的区别及应用场合

关于函数常规、防抖、节流三种执行方式的区别可以通过下面的例子直观的看出来

函数防抖和节流都能控制一段时间内函数执行的次数,简单的说,它们之间的区别及应用:

  • 函数防抖: 将本来短时间内爆发的一组事件组合成单个事件来触发。等电梯就是一个非常形象的比喻,电梯不会立即上行,而是等待一段时间内没有人再上电梯了才上行,换句话说此时函数执行时一阵一阵的,如果一直有人上电梯,电梯就永远不会上行。

使用场合:用户输入关键词实时搜索,如果用户每输入一个字符就发请求搜索一次,就太浪费网络,页面性能也差;再比如缩放浏览器窗口事件;再再比如页面滚动埋点

  • 函数节流: 控制持续快速触发的一系列事件每隔'X'毫秒执行一次,就像Magic把瓢泼大雨编程了绵绵细雨。

使用场合:页面滚动过程中不断统计离底部距离以便懒加载。

函数防抖与节流的简易实现

如果应用场合比较常规,根据上述函数防抖和节流的概念,代码实现还是比较简单的:
简易防抖工具函数实现如下:

  1. function debounce(func, wait) {
  2. let timerId
  3. return function(...args) {
  4. timerId && clearTimeout(timerId)
  5. timerId = setTimeout(() => {
  6. func.apply(this, args)
  7. }, wait)
  8. }
  9. }

防抖高阶函数实现很简单,瞄一眼就懂,但是仍要注意:代码第三行返回的函数并没有使用箭头函数,目的是在事件执行时确定上下文,节流的高阶函数实现起来相对复杂一点。

  1. function throttle(func, wait = 100) {
  2. let timerId
  3. let start = Date.now()
  4. return function(...args) {
  5. const curr = Date.now()
  6. clearTimeout(timerId)
  7. if (curr - start >= wait) {// 可以保证func一定会被执行
  8. func.apply(this, args)
  9. start = curr
  10. } else {
  11. timerId = setTimeout(() => {
  12. func.apply(this, args)
  13. }, wait)
  14. }
  15. }
  16. }

Lodash函数防抖(debounce)与节流(throttle)源码精读

上面的基本实现大致满足绝大多数场景的需求,但是Lodash库中的实现则更加完备,下面我们一起看看其源码实现。

  1. import isObject from "./isObject.js"
  2. import root from "./.internal/root.js"
  3. function debounce(func, wait, options) {
  4. /**
  5. * maxWait 最长等待执行时间
  6. * lastCallTime 事件上次触发的时间,由于函数防抖,真正的事件处理程序并不一定会执行
  7. */
  8. let lastArgs, lastThis, maxWait, result, timerId, lastCallTime
  9. let lastInvokeTime = 0 // 上一次函数真正调用的时间戳
  10. let leading = false // 是否在等待时间的起始端触发函数调用
  11. let maxing = false //
  12. let trailing = true // 是否在等待时间的结束端触发函数调用
  13. // 如果没有传入wait参数,检测requestAnimationFrame方法是否可以,以便后面代替setTimeout,默认等待时间约16ms
  14. const useRAF =
  15. !wait && wait !== 0 && typeof root.requestAnimationFrame === "function"
  16. if (typeof func != "function") {
  17. // 必须传入函数
  18. throw new TypeError("Expected a function")
  19. }
  20. wait = +wait || 0 // wait参数转换成数字,或设置默认值0
  21. if (isObject(options)) {
  22. // 规范化参数
  23. leading = !!options.leading
  24. maxing = "maxWait" in options
  25. maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait
  26. trailing = "trailing" in options ? !!options.trailing : trailing
  27. }
  28. // 调用真正的函数,入参是调用函数时间戳
  29. function invokeFunc(time) {
  30. const args = lastArgs
  31. const thisArg = lastThis
  32. lastArgs = lastThis = undefined
  33. lastInvokeTime = time
  34. result = func.apply(thisArg, args)
  35. return result
  36. }
  37. // 开启计时器方法,返回定时器id
  38. function startTimer(pendingFunc, wait) {
  39. if (useRAF) {
  40. // 如果没有传入wait参数,约16ms后执行
  41. return root.requestAnimationFrame(pendingFunc)
  42. }
  43. return setTimeout(pendingFunc, wait)
  44. }
  45. // 取消定时器
  46. function cancelTimer(id) {
  47. if (useRAF) {
  48. return root.cancelAnimationFrame(id)
  49. }
  50. clearTimeout(id)
  51. }
  52. //等待时间起始端调用事件处理程序
  53. function leadingEdge(time) {
  54. // Reset any `maxWait` timer.
  55. lastInvokeTime = time
  56. // Start the timer for the trailing edge.
  57. timerId = startTimer(timerExpired, wait)
  58. // Invoke the leading edge.
  59. return leading ? invokeFunc(time) : result
  60. }
  61. function remainingWait(time) {
  62. // 事件上次触发到现在的经历的时间
  63. const timeSinceLastCall = time - lastCallTime
  64. // 事件处理函数上次真正执行到现在经历的时间
  65. const timeSinceLastInvoke = time - lastInvokeTime
  66. // 等待触发的时间
  67. const timeWaiting = wait - timeSinceLastCall
  68. // 如果用户设置了最长等待时间,则需要取最小值
  69. return maxing
  70. ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke)
  71. : timeWaiting
  72. }
  73. // 判断某个时刻是否允许调用真正的事件处理程序
  74. function shouldInvoke(time) {
  75. const timeSinceLastCall = time - lastCallTime
  76. const timeSinceLastInvoke = time - lastInvokeTime
  77. return (
  78. lastCallTime === undefined || // 如果是第一次调用,则一定允许
  79. timeSinceLastCall >= wait || // 等待时间超过设置的时间
  80. timeSinceLastCall < 0 || // 当前时刻早于上次事件触发时间,比如说调整了系统时间
  81. (maxing && timeSinceLastInvoke >= maxWait) // 等待时间超过最大等待时间
  82. )
  83. }
  84. // 计时器时间到期执行的回调
  85. function timerExpired() {
  86. const time = Date.now()
  87. if (shouldInvoke(time)) {
  88. return trailingEdge(time)
  89. }
  90. // 重新启动计时器
  91. timerId = startTimer(timerExpired, remainingWait(time))
  92. }
  93. function trailingEdge(time) {
  94. timerId = undefined
  95. // 只有当事件至少发生过一次且配置了末端触发才调用真正的事件处理程序,
  96. // 意思是如果程序设置了末端触发,且没有设置最大等待时间,但是事件自始至终只触发了一次,则真正的事件处理程序永远不会执行
  97. if (trailing && lastArgs) {
  98. return invokeFunc(time)
  99. }
  100. lastArgs = lastThis = undefined
  101. return result
  102. }
  103. // 取消执行
  104. function cancel() {
  105. if (timerId !== undefined) {
  106. cancelTimer(timerId)
  107. }
  108. lastInvokeTime = 0
  109. lastArgs = lastCallTime = lastThis = timerId = undefined
  110. }
  111. // 立即触发一次事件处理程序调用
  112. function flush() {
  113. return timerId === undefined ? result : trailingEdge(Date.now())
  114. }
  115. // 查询是否处于等待执行中
  116. function pending() {
  117. return timerId !== undefined
  118. }
  119. function debounced(...args) {
  120. const time = Date.now()
  121. const isInvoking = shouldInvoke(time)
  122. lastArgs = args
  123. lastThis = this
  124. lastCallTime = time
  125. if (isInvoking) {
  126. if (timerId === undefined) {
  127. return leadingEdge(lastCallTime)
  128. }
  129. if (maxing) {
  130. // Handle invocations in a tight loop.
  131. timerId = startTimer(timerExpired, wait)
  132. return invokeFunc(lastCallTime)
  133. }
  134. }
  135. if (timerId === undefined) {
  136. timerId = startTimer(timerExpired, wait)
  137. }
  138. return result
  139. }
  140. debounced.cancel = cancel
  141. debounced.flush = flush
  142. debounced.pending = pending
  143. return debounced
  144. }
  145. export default debounce

Lodash中throttle直接使用debounce实现,说明节流可以当作防抖的一种特殊情况。

  1. function throttle(func, wait, options) {
  2. var leading = true,
  3. trailing = true;
  4. if (typeof func != 'function') {
  5. throw new TypeError(FUNC_ERROR_TEXT);
  6. }
  7. if (isObject(options)) {
  8. leading = 'leading' in options ? !!options.leading : leading;
  9. trailing = 'trailing' in options ? !!options.trailing : trailing;
  10. }
  11. return debounce(func, wait, {
  12. 'leading': leading,
  13. 'maxWait': wait,
  14. 'trailing': trailing
  15. });
  16. }

“浅入浅出”函数防抖(debounce)与节流(throttle)的更多相关文章

  1. 防抖debounce和节流throttle

    大纲 一.出现缘由 二.什么是防抖debounce和节流throttle 三.应用场景 3.1防抖 3.2节流 一.出现缘由 前端开发中,有一部分用户行为会频繁触发事件,而对于DOM操作,资源加载等耗 ...

  2. js 函数的防抖(debounce)与节流(throttle)

    原文:函数防抖和节流: 序言: 我们在平时开发的时候,会有很多场景会频繁触发事件,比如说搜索框实时发请求,onmousemove, resize, onscroll等等,有些时候,我们并不能或者不想频 ...

  3. js 函数的防抖(debounce)与节流(throttle) 带 插件完整解析版 [helpers.js]

    前言:         本人纯小白一个,有很多地方理解的没有各位大牛那么透彻,如有错误,请各位大牛指出斧正!小弟感激不尽.         函数防抖与节流是做什么的?下面进行通俗的讲解. 本文借鉴:h ...

  4. Java版的防抖(debounce)和节流(throttle)

    概念 防抖(debounce) 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时. 防抖,即如果短时间内大量触发同一事件,都会 ...

  5. 防抖(Debounce)与节流( throttle)区别

    http://www.cnblogs.com/ShadowLoki/p/3712048.html http://blog.csdn.net/tina_ttl/article/details/51830 ...

  6. js 防抖 debounce 与 节流 throttle

    debounce(防抖) 与 throttle(节流) 主要是用于用户交互处理过程中的性能优化.都是为了避免在短时间内重复触发(比如scrollTop等导致的回流.http请求等)导致的资源浪费问题. ...

  7. JavaScript 防抖(debounce)和节流(throttle)

    防抖函数 触发高频事件后,n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 /** * * @param {*} fn :callback function * @param {* ...

  8. 浅入浅出EmguCv(一)OpenCv与EmguCv

    最近接触计算机视觉方面的东西,于是准备下手学习opencv,从官网下载windows的安装版,配置环境,一系列步骤走完后,准备按照惯例弄个HelloWord.也就是按照网上的教程,打开了那个图像处理领 ...

  9. 浅入深出之Java集合框架(中)

    Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  10. 包学会之浅入浅出Vue.js:结业篇(转)

    蔡述雄,现腾讯用户体验设计部QQ空间高级UI工程师.智图图片优化系统首席工程师,曾参与<众妙之门>书籍的翻译工作.目前专注前端图片优化与新技术的探研. 在第一篇<包学会之浅入浅出Vu ...

随机推荐

  1. 快速理解js中的call,apply的作用

    今天被人问到js中的call,apply的区别和用途,解释了一番后,想到之前在逼乎上看到一位小伙伴生动形象的解释 本身不难理解,看下MDN就知道了,但是不常用,遇到了,还要脑回路回转下.或者时间长了, ...

  2. SDOI2019游记

    Day0 一大早就起床,结果忙活了整整一上午. 12:20从gryz出发,路上发现把耳机和笔忘另一个背包里了(都怪老爸非得让我换背包),15:30差不多就到山师了. 山师也是蛮漂亮的,花开得挺好.到处 ...

  3. vue 中的通过搜索框进行数据过滤的过程

    <template> <div> <input type="text" v-model="searchId" placeholde ...

  4. Unity 子弹移动以及碰撞条件

    一.子弹移动 游戏物体移动最主要的是获取一个刚体组件,再对这个刚体组件添加一个向前的力: 具体代码: public class BulletCtrl : MonoBehaviour { ; publi ...

  5. 3DMAX中坐标解析

    World:世界坐标系,又称世界空间.位于各视口左下角的图标,显示了世界坐标系的方向,其坐标原点位于视口中心.该坐标系永远不会变化. Screen:屏幕坐标系,此时将使用活动视口屏幕作为坐标系.在活动 ...

  6. 关于macOS 管理员(Admin)权限问题。

    最近突然想改下用户名,于是在用户与组里解锁,然后两个手指点击用户那一行,更改fullname,不过出于好奇把uid和uuid也改了. 之后发现current user等级由Admin变成Standar ...

  7. 浏览器将URL变成一个屏幕上显示的网页的过程?

    前言 一个浏览器是怎么工作的? 正文 URL变网页过程: 1.浏览器通过http或https协议,向服务端请求页面 2.将请求过来的HEML代码通过解析,构建DOM树 3.计算DOM树上的CSS属性 ...

  8. CPM、CPC、CPA、CPS、CPL、CPR 是什么意思 -解析互联网广告术语

    CPA CPS CPA/CPS常见的推广方式 CPA和CPSCPA,CPS CPS与CPA CPA.CPSCPA.CPS产品教  CPA CPS什么意思 CPACPS是什么 1. CPM(Cost p ...

  9. C语言通讯录系统——C语言单向链表实现

    实现的通讯录功能有:查看通讯录.添加联系人.删除联系人.查询联系人.保存并退出. 通过txt文件保存和读取通讯录数据. #include <stdio.h> #include <st ...

  10. 413 重温HTML + css 考试 + 访问HTML元素

    考试前的复习 初学css1:认识CSS 1.1:css简介,css全称是层叠样式表,Cascading style sheets 1.2:css的作用,主要是用于定义html内容在浏览器内的显示样式, ...