网易云课堂 > 微专业 > 前端高级开发工程师

01.前端高级-JavaScript进阶 > 3.函数式编程 Underscore源码分析 > 3.4.3 throttle 与 debounce 概念解析源码实现

CoderMonkie



1.认识throttle(节流)与debounce(防抖)

throttle(节流)与debounce(防抖)

throttledebounce是解决请求和响应速度不匹配问题的两个方案。

二者的差异在于选择不同的策略。

debounce的关注点是空闲的间隔时间,

throttle的关注点是连续的执行间隔时间。

应用场景

只要涉及到连续事件或频率控制相关的应用就可以考虑使用这两个函数,比如:

  • 游戏设计,keydown事件
  • 文本输入、自动完成,keyup事件
  • 鼠标移动mousemove事件
  • DOM元素动态定位,window对象的resizescroll事件

前两者,debouncethrottle都可以按需使用;

后两者就要用throttle

(以上摘自网易云课堂微专业前端高级课程)

上手试试

很容易想到的应用场景就是,用户输入,

wait间隔毫秒数,就是用户操作停顿的事件,

如果间隔非常小,就视为没有停顿;

如果超过设定的值,就认为停顿了,发起web请求。

但我们这里用页面滚动scroll事件来举例(观测简便)。

没有防抖与节流的操作

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>debounce sample</title>
  8. </head>
  9. <body>
  10. <div style="height: 1500px;"></div>
  11. <script>
  12. // scroll in normal
  13. window.addEventListener('scroll', function(){
  14. console.log('normal scrolling...')
  15. })
  16. </script>
  17. </body>
  18. </html>

打开样例页面的控制台并滚动鼠标滚动轮,会发现随着滚动不停地打印出log信息。

debounce

  • 原理

    对于设定的间隔时间(通常为毫秒)内的交互,只响应最新的,之前的全部忽略掉。

    用一个形象的例子来说明:

    用户(比如你)跟baidu说,我想搜索点不可告人的学习资料,

    baidu:你确定吗?1500毫秒内不回复的话,我就检索了

    --未超过1500毫秒,重新/继续 输入--

    用户(比如你):更改搜索内容为“JS中防抖节流的原理与应用”

    baidu:你确定吗?1500毫秒内不回复的话,我就检索了

    --超过1500毫秒未输入--

    baidu:已发起检索请求,这是结果(JS中防抖节流的原理与应用)

    --此时再输入--

    用户(比如你):我还是想看点令人愉悦的龌龊内容

    --回到对话开始时的状态,以下重复省略--

    1500毫秒只是打个比方,实际比这个值要小

    (其实在搜索中,防抖与节流都有使用,后面会提到)

  • 手写一下简单的debounce函数实现

    1. // 简单的`debounce`函数实现
    2. var debounce = function(func, wait){
    3. var timer // 定时器
    4. // 返回包装过的debounce函数
    5. return function(...args){
    6. // 如果有触发,则取消之前的触发,以当前触发为准,重新计时
    7. if(timer){
    8. clearTimeout(timer)
    9. }
    10. // 设置定时器
    11. timer = setTimeout(function(){
    12. // 定时器的回调函数:清除本次定时器,并执行函数
    13. clearTimeout(timer)
    14. timer = null
    15. func.apply(null, args)
    16. }, wait)
    17. }
    18. }

使用debounce的例子

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>debounce sample</title>
  8. </head>
  9. <body>
  10. <div style="height: 1500px;"></div>
  11. <script>
  12. // debounce-definition
  13. // scroll in debounce
  14. var debounceFnc = debounce(function(){
  15. console.log('scroll in debounce')
  16. }, 1500)
  17. window.addEventListener('scroll', debounceFnc)
  18. </script>
  19. </body>
  20. </html>

打开上面例子页面的控制台,可以看到,当滚动鼠标滚动轮(或拖动滚动条),

1500毫秒内,不管滚动多少次,都会在停止并经过1500毫秒后,只执行一次。

throttle

  • 原理

    达到设定的时间间隔才可以触发,控制调用的频率。

    用一个形象的例子来说明,

    就像是游戏中放大后的冷却,放一个大之后,

    大招就不可用了,需要等待冷却时间过后才可以再发一次。

  • 手写一个简单的throttle函数实现

    1. // 简单的`throttle`函数实现
    2. var throttle = function(func, wait){
    3. var lastTime = 0 // 用来记录上次执行的时刻
    4. // 返回包装过的throttle函数
    5. return function(...args){
    6. var now = Date.now()
    7. var coolingDown = now - lastTime < wait
    8. // ↑ 距离上次执行的间隔,小于设定的间隔时间 => 则处于冷却时间
    9. // 冷却时间,禁止放大招
    10. if(coolingDown){
    11. return
    12. }
    13. // 记录本次执行的时刻
    14. lastTime = Date.now()
    15. // 冷却好了就要放大招
    16. func.apply(null, args)
    17. }
    18. }

使用throttle的例子

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>throttle sample</title>
  8. </head>
  9. <body>
  10. <div style="height: 1500px;"></div>
  11. <script>
  12. // throttle-definition
  13. // scroll in throttle
  14. var throttleFnc = throttle(function(){
  15. console.log('scroll in throttle')
  16. }, 1500)
  17. window.addEventListener('scroll', throttle)
  18. </script>
  19. </body>
  20. </html>

打开上面例子页面的控制台,可以看到,当滚动鼠标滚动轮(或拖动滚动条),

不管滚动地多快甚至一直滚动不停,每次执行(打印log信息)都会间隔1500毫秒。

小小总结

对比上面的debounce防抖与throttle节流,

  • 相同

    • debounce防抖与throttle节流都实现了单位时间内,函数只执行一次
  • 不同
    • debounce防抖:

      单位时间内,忽略前面的,响应最新的,并在延迟wait毫秒后执行
    • throttle节流:

      响应第一次的,单位时间内,不再响应,直到wait毫秒后才再响应

咱之前没做过前端(实际上是就没怎么接触过web),

就没听说过underscore这个工具库,直到在网易云课堂·微专业

学习前端高级开发工程师的课程,才认识到了underscore

(广告过后,)咱们来看看underscore中可配置的throttle节流与debounce防抖。


2.JavaScript工具箱underscore中的throttle(节流)与debounce(防抖)

看了上面简单实现的代码样例,让我们再来了解下underscore中的节流与防抖

引入underscore.js文件或cdn地址:

<script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore-min.js"></script>

1) throttle 节流

  1. _.throttle(function, wait, [options])
参数列表
function 处理函数
wait 指定的毫秒数间隔
options 配置
可选。默认:{ leading: false, trailing: false }

针对第一次触发,

leading : true 相当于先执行,再等待wait毫秒之后才可再次触发

trailing : true 相当于先等待wait毫秒,后执行

默认:

leading : false => 阻止第一次触发时立即执行,等待wait毫秒才可触发

trailing : false => 阻止第一次触发时的延迟执行,经过延迟的wait毫秒之后才可触发

可能的配置方式:

(区别在首次执行和先执行还是先等待)

配置 结果
{ leading: false, trailing: false } 第一次触发不执行,后面同普通throttle,执行 + 间隔wait毫秒
{ leading: true, trailing: false } 第一次触发立即执行,后面同普通throttle,执行 + 间隔wait毫秒
{ leading: false, trailing: true } 每次触发延迟执行,每次执行间隔wait毫秒
{ leading: true, trailing: true } 每一次有效触发都会执行两次,先立即执行一次,后延时wait毫秒执行一次

知道了原理,我们来简单写一下代码实现。

  1. _.now = Date.now
  2. _.throttle = function(func, wait, options){
  3. var lastTime = 0
  4. var timeOut = null
  5. var result
  6. if(!options){
  7. options = { leading: false, trailing: false }
  8. }
  9. return function(...args){ // 节流函数
  10. var now = _.now()
  11. // 首次执行看是否配置了 leading = false = 默认,阻止立即执行
  12. if(!lastTime && options.leading === false){
  13. lastTime = now
  14. }
  15. // 配置了 leading = true 时,初始值 lastTime = 0,即可以立即执行
  16. var remaining = lastTime + wait - now
  17. // > 0 即间隔内
  18. // < 0 即超出间隔时间
  19. // 超出间隔时间,或首次的立即执行
  20. if(remaining <= 0){ // trailing=false
  21. if(timeOut){
  22. // 如果不是首次执行的情况,需要清空定时器
  23. clearTimeout(timeOut)
  24. timeOut = null
  25. }
  26. lastTime = now // #
  27. result = func.apply(null, args)
  28. }
  29. else if(!timeOut && options.trailing !== false){ // leading
  30. // 没超出间隔时间,但配置了 leading=fasle 阻止了立即执行,
  31. // 即需要执行一次却还未执行,等待中,且配置了 trailing=true
  32. // 那就要在剩余等待毫秒时间后触发
  33. timeOut = setTimeout(()=>{
  34. lastTime = options.leading === false ? 0 : _.now() // # !lastTime 的判断中需要此处重置为0
  35. timeOut = null
  36. result = func.apply(null, args)
  37. }, remaining);
  38. }
  39. return result
  40. }
  41. }

2) debounce 防抖

  1. _.debounce(func, wait, [immediate])
function 处理函数
wait 指定的毫秒数间隔
immediate 立即执行Flag
可选。默认:false

功能解析:

前两个参数与前面介绍的throttle是一样的,第三个参数,

immediate指定第一次触发或没有等待中的时候可以立即执行。

知道了原理,我们来简单写一下代码实现。

  1. // debounce 防抖:
  2. // 用户停止输入&wait毫秒后,响应,
  3. // 或 immediate = true 时,没有等待的回调的话立即执行
  4. // 立即执行并不影响去设定时器延迟执行
  5. _.debounce = function(func, wait, immediate){
  6. var timer, result
  7. var later = function(...args){
  8. clearTimeout(timer)
  9. timer = null
  10. result = func.apply(null, args)
  11. }
  12. return function(...args){
  13. // 因为防抖是响应最新一次操作,所以清空之前的定时器
  14. if(timer) clearTimeout(timer)
  15. // 如果配置了 immediate = true
  16. if(immediate){
  17. // 没有定时函数等待执行才可以立即执行
  18. var callNow = !timer
  19. // 是否立即执行,并不影响设定时器的延迟执行
  20. timer = setTimeout(later, wait, ...args)
  21. if(callNow){
  22. result = func.apply(null, args)
  23. }
  24. }
  25. else{
  26. timer = setTimeout(later, wait, ...args)
  27. }
  28. return result
  29. }
  30. }

代码添加了注释作为说明内容,应该很容易理解。


underscore.js最新源码(2019-08-22当前:1.9.1)的话会发现,

除了上文介绍的配置,还加入了可取消功能(cancel)(from 1.9.0)


3.重新审视throttle(节流)与debounce(防抖)

underscore.js中通过增加可配置项来实现精细控制以应对使用者的不同需求。

其实我们一般的需求,应该是这两种基础功能结合在一起应用。

再回到最初我们自己写的极简示例demo函数上,

在没有任何配置的基本的实现里,debouncethrottle的区别在于,

当鼠标一直在滚动,debounce会一直等待结束后wait毫秒再执行,

throttle会每间隔wait毫秒就执行一次。

再比如还是以用户输入为例,极端的例子,一直输入没有停的情况下,

输入了十分钟,结果页面在这十分钟内是没有反应的,

停止输入并经过wait毫秒之后,才会有响应,针对这种极端情况,

就用到了debouncethrottle组合。

一直输入的过程中,按照debounce是不会触发响应的,但超过了节流阀

throttle设定的wait,那至少会执行一次。

这样,就完善了用户输入的体验。

其它交互同理,一般将两者结合使用。

Talk is cheep, show you the code.

  1. // --------------------------------------------------
  2. function combineDebounceThrottle(func, wait){
  3. var lastTime = 0
  4. var timeoutD
  5. var timeoutT
  6. var later = function(...args){
  7. clearTimeout(timeoutD)
  8. clearTimeout(timeoutT)
  9. timeoutD = null
  10. timeoutT = null
  11. lastTime = Date.now()
  12. func.apply(null, args)
  13. }
  14. return function(...args){
  15. var now = Date.now()
  16. var coolingDown = now - lastTime < wait
  17. clearTimeout(timeoutD)
  18. if(!timeoutT && !coolingDown){
  19. timeoutT = setTimeout(later, wait, 'throttle',...args)
  20. }
  21. else{
  22. timeoutD = setTimeout(later, wait, 'debounce',...args)
  23. }
  24. }
  25. }
  26. // --------------------------------------------------
  27. var func = combineDebounceThrottle(function(logFlag){
  28. console.log('scrolling in throttle-debounce-combined')
  29. }, 1500)
  30. window.addEventListener('scroll', func)
  31. // --------------------------------------------------

啰嗦两句,

只用 debounce 的话,一直滚动就会一直等待,

加入了 throttle,则超过 wait 毫秒就会执行一次,throttle 延迟执行等待中的时候,

仍然有输入则在结束后,还会触发 debounce 的延时回调。


--END--


[JavaScript] 节流(throttle)-防抖(debounce) 不懵圈指北的更多相关文章

  1. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

  2. javaScript 节流与防抖

    首先 我们要知道 节流与防抖可以干什么. 优化网络请求性能——节流 优化页面请求性能——防抖 举两个简单的小例子: 节流: 例如 有些购物页面,会有一些让你抢购的活动,到点的时候,需要你快速的点某个按 ...

  3. JavaScript节流与防抖函数封装

    js节流与防抖函数封装 常见应用场景: window的 resize 和 scroll 事件: 文字输入时的 keyup 事件: 元素拖拽.移动时的 mousemove 事件: 防抖 定义:多次触发事 ...

  4. javaScript节流与防抖

    一.节流(throttle) 用来实现阻止在短时间内重复多次触发同一个函数.主要用途:防止使用脚本循环触发网络请求的函数的恶意行为,确保请求的真实性(当然也包括其他阻止高频触发行为的应用): 实现原理 ...

  5. 精简的javascript下throttle和debounce代码

    //频率控制 函数连续调用时,fn 执行频率限定为 1次/waitMs.立即执行1次 function throttle(fn, waitMs) { var lastRun = 0; return f ...

  6. JavaScript 事件循环及异步原理(完全指北)

    引言 最近面试被问到,JS 既然是单线程的,为什么可以执行异步操作? 当时脑子蒙了,思维一直被困在 单线程 这个问题上,一直在思考单线程为什么可以额外运行任务,其实在我很早以前写的博客里面有写相关的内 ...

  7. 防抖debounce和节流throttle

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

  8. JavaScript 中的防抖和节流

    什么是防抖 函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时.如下图,持续触发 scrol ...

  9. [概念] js的函数节流和throttle和debounce详解

    js的函数节流和throttle和debounce详解:同样是实现了一个功能,可能有的效率高,有的效率低,这种现象在高耗能的执行过程中区分就比较明显.本章节一个比较常用的提高性能的方式,通常叫做&qu ...

随机推荐

  1. 【题解】长度为素数的路径个数-C++

    Description 对于正整数n (3≤n<20),可以画出n阶的回形矩阵.下面画出的分别是3阶的,4阶的和7阶的回形矩阵: 对于n阶回形矩阵,从左上角出发,每步可以向右或向下走一格,走2* ...

  2. pgsql查询优化之模糊查询

    前言 一直以来,对于搜索时模糊匹配的优化一直是个让人头疼的问题,好在强大pgsql提供了优化方案,下面就来简单谈一谈如何通过索引来优化模糊匹配 案例 我们有一张千万级数据的检查报告表,需要通过检查报告 ...

  3. 【CYH-01】小奔的国庆练习赛:赛后标程

    前排鸣谢@找寻 大佬 emm-由于头一次举办公开赛所以--准备不是很充分,所以说题解也没有备好,在这里表示歉意. 欢迎大家来发布题解,在此我们可以提供AC代码,供大家参考. T1 解析:这一题可能栈溢 ...

  4. MapRedue详细工作流程

    MapRedue详细工作流程 简述 (1)客户端submit之前获取待处理的数据信息,根据参数配置,形成一个任务分配的规划. (2)提交切片信息到YARN(split.xml,job.split,wc ...

  5. 在工作中常用的Linux命令

    前言 只有光头才能变强. 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 之前写过一篇 < 在公司做的项目和自己在学校做 ...

  6. Oracle导入dump文件

    imp USER/PASSWORD@host/DB_name file=xxx.dmp(path) full=y ignore=y

  7. ssm框架下的文件上传和文件下载

    最近在做一个ssm的项目,遇到了添加附件和下载的功能,在网上查了很多资料,发现很多都不好用,经过摸索,发现了一套简便的方法,和大家分享一下. 1.在自己已经构建好的maven  web项目中 pom. ...

  8. 如何简单地利用Bitmap为中介储存图片到数据库中

        这是我的第一篇博文,请大家多多指教!     大概一个月之前,在跟朋友合作开发一个APP的过程中,我们发现到一个问题:图片的存储.因为数据库没有图片这种数据类型,当用户上传的图片需要存储的时候 ...

  9. Apache Ignite 学习笔记(6): Ignite中Entry Processor使用

    之前的文章我们其实已经用到了两种不同的方式访问Ignite中的数据.一种方式是第一篇文章中提到通过JDBC客户端用SQL访问数据,在这篇文章中我们也会看到不使用JDBC,如何通过Ignite API用 ...

  10. 03-k8s认证

    目录 k8s认证 客户端 ---> API Server 外部访问 pod 客户端 RBCA k8s 用户类型 dashboard 的认证登录 k8s认证 主要使用 RBAC授权检查机制 认证: ...