[JavaScript] 节流(throttle)-防抖(debounce) 不懵圈指北
01.前端高级-JavaScript进阶 > 3.函数式编程 Underscore源码分析 > 3.4.3 throttle 与 debounce 概念解析源码实现
CoderMonkie
1.认识throttle
(节流)与debounce
(防抖)
throttle
(节流)与debounce
(防抖)
throttle
和debounce
是解决请求和响应速度不匹配问题的两个方案。
二者的差异在于选择不同的策略。
debounce
的关注点是空闲的间隔时间,
throttle
的关注点是连续的执行间隔时间。
应用场景
只要涉及到连续事件或频率控制相关的应用就可以考虑使用这两个函数,比如:
- 游戏设计,
keydown
事件 - 文本输入、自动完成,
keyup
事件 - 鼠标移动
mousemove
事件 DOM
元素动态定位,window
对象的resize
、scroll
事件
前两者,debounce
和throttle
都可以按需使用;
后两者就要用throttle
了
(以上摘自网易云课堂微专业前端高级课程)
上手试试
很容易想到的应用场景就是,用户输入,
wait
间隔毫秒数,就是用户操作停顿的事件,
如果间隔非常小,就视为没有停顿;
如果超过设定的值,就认为停顿了,发起web
请求。
但我们这里用页面滚动scroll
事件来举例(观测简便)。
没有防抖与节流的操作
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>debounce sample</title>
</head>
<body>
<div style="height: 1500px;"></div>
<script>
// scroll in normal
window.addEventListener('scroll', function(){
console.log('normal scrolling...')
})
</script>
</body>
</html>
打开样例页面的控制台并滚动鼠标滚动轮,会发现随着滚动不停地打印出log
信息。
debounce
原理
对于设定的间隔时间(通常为毫秒)内的交互,只响应最新的,之前的全部忽略掉。
用一个形象的例子来说明:
用户(比如你)跟baidu说,我想搜索点不可告人的学习资料,
baidu:你确定吗?1500毫秒内不回复的话,我就检索了
--未超过1500毫秒,重新/继续 输入--
用户(比如你):更改搜索内容为“JS中防抖节流的原理与应用”
baidu:你确定吗?1500毫秒内不回复的话,我就检索了
--超过1500毫秒未输入--
baidu:已发起检索请求,这是结果(JS中防抖节流的原理与应用)
--此时再输入--
用户(比如你):我还是想看点令人愉悦的龌龊内容
--回到对话开始时的状态,以下重复省略--
1500毫秒只是打个比方,实际比这个值要小(其实在搜索中,防抖与节流都有使用,后面会提到)
手写一下简单的
debounce
函数实现// 简单的`debounce`函数实现
var debounce = function(func, wait){
var timer // 定时器 // 返回包装过的debounce函数
return function(...args){
// 如果有触发,则取消之前的触发,以当前触发为准,重新计时
if(timer){
clearTimeout(timer)
} // 设置定时器
timer = setTimeout(function(){
// 定时器的回调函数:清除本次定时器,并执行函数
clearTimeout(timer)
timer = null
func.apply(null, args)
}, wait)
}
}
使用
debounce
的例子
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>debounce sample</title>
</head>
<body>
<div style="height: 1500px;"></div>
<script>
// debounce-definition
// scroll in debounce
var debounceFnc = debounce(function(){
console.log('scroll in debounce')
}, 1500)
window.addEventListener('scroll', debounceFnc)
</script>
</body>
</html>
打开上面例子页面的控制台,可以看到,当滚动鼠标滚动轮(或拖动滚动条),
1500毫秒内,不管滚动多少次,都会在停止并经过1500毫秒后,只执行一次。
throttle
原理
达到设定的时间间隔才可以触发,控制调用的频率。
用一个形象的例子来说明,
就像是游戏中放大后的冷却,放一个大之后,
大招就不可用了,需要等待冷却时间过后才可以再发一次。手写一个简单的
throttle
函数实现// 简单的`throttle`函数实现
var throttle = function(func, wait){
var lastTime = 0 // 用来记录上次执行的时刻 // 返回包装过的throttle函数
return function(...args){
var now = Date.now() var coolingDown = now - lastTime < wait
// ↑ 距离上次执行的间隔,小于设定的间隔时间 => 则处于冷却时间 // 冷却时间,禁止放大招
if(coolingDown){
return
} // 记录本次执行的时刻
lastTime = Date.now() // 冷却好了就要放大招
func.apply(null, args)
}
}
使用
throttle
的例子
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>throttle sample</title>
</head>
<body>
<div style="height: 1500px;"></div>
<script>
// throttle-definition
// scroll in throttle
var throttleFnc = throttle(function(){
console.log('scroll in throttle')
}, 1500)
window.addEventListener('scroll', throttle)
</script>
</body>
</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 节流
_.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 毫秒执行一次 |
知道了原理,我们来简单写一下代码实现。
_.now = Date.now
_.throttle = function(func, wait, options){
var lastTime = 0
var timeOut = null
var result
if(!options){
options = { leading: false, trailing: false }
}
return function(...args){ // 节流函数
var now = _.now()
// 首次执行看是否配置了 leading = false = 默认,阻止立即执行
if(!lastTime && options.leading === false){
lastTime = now
}
// 配置了 leading = true 时,初始值 lastTime = 0,即可以立即执行
var remaining = lastTime + wait - now
// > 0 即间隔内
// < 0 即超出间隔时间
// 超出间隔时间,或首次的立即执行
if(remaining <= 0){ // trailing=false
if(timeOut){
// 如果不是首次执行的情况,需要清空定时器
clearTimeout(timeOut)
timeOut = null
}
lastTime = now // #
result = func.apply(null, args)
}
else if(!timeOut && options.trailing !== false){ // leading
// 没超出间隔时间,但配置了 leading=fasle 阻止了立即执行,
// 即需要执行一次却还未执行,等待中,且配置了 trailing=true
// 那就要在剩余等待毫秒时间后触发
timeOut = setTimeout(()=>{
lastTime = options.leading === false ? 0 : _.now() // # !lastTime 的判断中需要此处重置为0
timeOut = null
result = func.apply(null, args)
}, remaining);
}
return result
}
}
2) debounce 防抖
_.debounce(func, wait, [immediate])
function | 处理函数 |
wait | 指定的毫秒数间隔 |
immediate | 立即执行Flag |
可选。默认:false |
功能解析:
前两个参数与前面介绍的throttle
是一样的,第三个参数,
immediate
指定第一次触发或没有等待中的时候可以立即执行。
知道了原理,我们来简单写一下代码实现。
// debounce 防抖:
// 用户停止输入&wait毫秒后,响应,
// 或 immediate = true 时,没有等待的回调的话立即执行
// 立即执行并不影响去设定时器延迟执行
_.debounce = function(func, wait, immediate){
var timer, result
var later = function(...args){
clearTimeout(timer)
timer = null
result = func.apply(null, args)
}
return function(...args){
// 因为防抖是响应最新一次操作,所以清空之前的定时器
if(timer) clearTimeout(timer)
// 如果配置了 immediate = true
if(immediate){
// 没有定时函数等待执行才可以立即执行
var callNow = !timer
// 是否立即执行,并不影响设定时器的延迟执行
timer = setTimeout(later, wait, ...args)
if(callNow){
result = func.apply(null, args)
}
}
else{
timer = setTimeout(later, wait, ...args)
}
return result
}
}
代码添加了注释作为说明内容,应该很容易理解。
看
underscore.js
最新源码(2019-08-22当前:1.9.1)的话会发现,
除了上文介绍的配置,还加入了可取消功能(cancel)(from 1.9.0)
3.重新审视throttle
(节流)与debounce
(防抖)
underscore.js
中通过增加可配置项来实现精细控制以应对使用者的不同需求。
其实我们一般的需求,应该是这两种基础功能结合在一起应用。
再回到最初我们自己写的极简示例demo
函数上,
在没有任何配置的基本的实现里,debounce
与throttle
的区别在于,
当鼠标一直在滚动,debounce
会一直等待结束后wait
毫秒再执行,
而throttle
会每间隔wait
毫秒就执行一次。
再比如还是以用户输入为例,极端的例子,一直输入没有停的情况下,
输入了十分钟,结果页面在这十分钟内是没有反应的,
停止输入并经过wait
毫秒之后,才会有响应,针对这种极端情况,
就用到了debounce
与throttle
组合。
一直输入的过程中,按照debounce
是不会触发响应的,但超过了节流阀
throttle
设定的wait
,那至少会执行一次。
这样,就完善了用户输入的体验。
其它交互同理,一般将两者结合使用。
Talk is cheep, show you the code.
// --------------------------------------------------
function combineDebounceThrottle(func, wait){
var lastTime = 0
var timeoutD
var timeoutT
var later = function(...args){
clearTimeout(timeoutD)
clearTimeout(timeoutT)
timeoutD = null
timeoutT = null
lastTime = Date.now()
func.apply(null, args)
}
return function(...args){
var now = Date.now()
var coolingDown = now - lastTime < wait
clearTimeout(timeoutD)
if(!timeoutT && !coolingDown){
timeoutT = setTimeout(later, wait, 'throttle',...args)
}
else{
timeoutD = setTimeout(later, wait, 'debounce',...args)
}
}
}
// --------------------------------------------------
var func = combineDebounceThrottle(function(logFlag){
console.log('scrolling in throttle-debounce-combined')
}, 1500)
window.addEventListener('scroll', func)
// --------------------------------------------------
啰嗦两句,
只用 debounce 的话,一直滚动就会一直等待,
加入了 throttle,则超过 wait 毫秒就会执行一次,throttle 延迟执行等待中的时候,
仍然有输入则在结束后,还会触发 debounce 的延时回调。
--END--
[JavaScript] 节流(throttle)-防抖(debounce) 不懵圈指北的更多相关文章
- c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)
c#封装DBHelper类 public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...
- javaScript 节流与防抖
首先 我们要知道 节流与防抖可以干什么. 优化网络请求性能——节流 优化页面请求性能——防抖 举两个简单的小例子: 节流: 例如 有些购物页面,会有一些让你抢购的活动,到点的时候,需要你快速的点某个按 ...
- JavaScript节流与防抖函数封装
js节流与防抖函数封装 常见应用场景: window的 resize 和 scroll 事件: 文字输入时的 keyup 事件: 元素拖拽.移动时的 mousemove 事件: 防抖 定义:多次触发事 ...
- javaScript节流与防抖
一.节流(throttle) 用来实现阻止在短时间内重复多次触发同一个函数.主要用途:防止使用脚本循环触发网络请求的函数的恶意行为,确保请求的真实性(当然也包括其他阻止高频触发行为的应用): 实现原理 ...
- 精简的javascript下throttle和debounce代码
//频率控制 函数连续调用时,fn 执行频率限定为 1次/waitMs.立即执行1次 function throttle(fn, waitMs) { var lastRun = 0; return f ...
- JavaScript 事件循环及异步原理(完全指北)
引言 最近面试被问到,JS 既然是单线程的,为什么可以执行异步操作? 当时脑子蒙了,思维一直被困在 单线程 这个问题上,一直在思考单线程为什么可以额外运行任务,其实在我很早以前写的博客里面有写相关的内 ...
- 防抖debounce和节流throttle
大纲 一.出现缘由 二.什么是防抖debounce和节流throttle 三.应用场景 3.1防抖 3.2节流 一.出现缘由 前端开发中,有一部分用户行为会频繁触发事件,而对于DOM操作,资源加载等耗 ...
- JavaScript 中的防抖和节流
什么是防抖 函数防抖(debounce):当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定的时间到来之前,又一次触发了事件,就重新开始延时.如下图,持续触发 scrol ...
- [概念] js的函数节流和throttle和debounce详解
js的函数节流和throttle和debounce详解:同样是实现了一个功能,可能有的效率高,有的效率低,这种现象在高耗能的执行过程中区分就比较明显.本章节一个比较常用的提高性能的方式,通常叫做&qu ...
随机推荐
- 自定义SSL证书实现单双向ssl认证记录
自定义SSL证书: 1.ca证书 #openssl genrsa -out ca.key 2048 #openssl req -new -key ca.key -out ca.csr #openssl ...
- Netty-Pipeline深度解析
首先我们知道,在NIO网络编程模型中,IO操作直接和channel相关,比如客户端的请求连接,或者向服务端发送数据, 服务端都要从客户端的channel获取这个数据 那么channelPipeline ...
- 洛谷P1129 [ZJOI2007]矩阵游戏 题解
题目链接:https://www.luogu.org/problemnew/show/P1129 分析: 这道题不是很好想,但只要想的出来,代码不成问题. 思路1 举几个例子,我们发现, 对于任何数来 ...
- 我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3cp8ng15g94wc
我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=3cp8ng15g94wc
- 从后端到前端之Vue(三)小结以及一颗真实的大树
上一篇写了一下tab,下面整理一下用过的知识点,本想按照官网的文档,整理一下可以更清晰,结果也许是我的方法不对吧,总之更模糊了. 按照官网文档的顺序整理到了表单输入绑定之前,因为之前大致也就只涉及到这 ...
- an introduction of google breakPad for android
一.背景 众所周知,Android JNI层的Crash问题是个比较头疼的问题.相对Java层来说,由于c/c++造成的crash没有输出如同 Java的Exception Strace,所以cras ...
- MetInfo企业网站管理系统 5.3 全新安装
在phpStudy\PHPTutorial\WWW的文件下创建MetInfo文件夹.把MetInfo5.3解压到MetInfo文件夹里 用浏览器访问127.0.0.1/MetInfo 同意安装 下一步 ...
- UPC Contest RankList – 2019年第二阶段我要变强个人训练赛第十五场
传送门 A: Colorful Subsequence •题意 给一个长为n的小写字母序列,从中选出字母组成子序列 问最多能组成多少种每个字母都不相同的子序列 (不同位置的相同字母也算是不同的一种) ...
- 确保Web安全的HTTPS
HTTP在安全方面主要有以下不足: 1. 通信使用明文不加密,内容可能会被窃听:(TCP/IP就是可能被窃听的网络) 2. 不验证通信方的身份,因此有可能遭遇伪装: (无法判断请求或响应是否正确,是否 ...
- activeMQ_helloworld(一)
一.activeMQ下载,直接在Linux上wget http://mirror.bit.edu.cn/apache//activemq/5.14.5/apache-activemq-5.14.5-b ...