[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 ...
随机推荐
- Excel催化剂开源第39波-json字符串解释的超能类库
对一般VBA开发群体来说,处理json.xml结构的数据源,在VB6的世界里,是一件非常不容易的事情,隐约记得当年自己从哪里找到了一个使用字典实现的json解释的函数,实在非常稀有. 在.Net的世界 ...
- [leetcode] 87. Scramble String (Hard)
题意: 判断两个字符串是否互为Scramble字符串,而互为Scramble字符串的定义: 字符串看作是父节点,从字符串某一处切开,生成的两个子串分别是父串的左右子树,再对切开生成的两个子串继续切开, ...
- 机器学习之K均值聚类
聚类的核心概念是相似度或距离,有很多相似度或距离的方法,比如欧式距离.马氏距离.相关系数.余弦定理.层次聚类和K均值聚类等 1. K均值聚类思想 K均值聚类的基本思想是,通过迭代的方法寻找K个 ...
- C#中使用split分割字符串的方法小结
string s=abcdeabcdeabcde; string[] sArray=s.Split(c) ; foreach(string i in sArray) Console.WriteLine ...
- python课堂整理5---元组
一.元组 Tuple tu = (111, 22, 33, "alex", (11,22), [(33, 44)], True, ) 元组元素不可被修改,不能被增加或删除 一般 ...
- k8s1.9.0安装--完整集群部署
三.完整集群部署 - kubernetes-with-ca 1. 理解认证授权 1.1 为什么要认证 想理解认证,我们得从认证解决什么问题.防止什么问题的发生入手.防止什么问题呢?是防止有人入侵你的集 ...
- Linux 下实践 VxLAN:虚拟机和 Docker 场景
本文首发于我的公众号 Linux云计算网络(id: cloud_dev),专注于干货分享,号内有 10T 书籍和视频资源,后台回复 「1024」 即可领取,欢迎大家关注,二维码文末可以扫. 在上篇文章 ...
- PHP Composer安装使用
1.安装composer curl -sS https:\\getcomposer.org/install | php 如果出现这样的提示,打开php.ini检查是否开启openssl扩展 2.下载成 ...
- 灵活使用Maven Profile
项目中一直应用Maven的profile特性解决不同环境的部署问题.最近在尝试解决本地调试环境的时候碰到一些问题,顺便仔细研究了一下.因为项目仍然在用普通SpringMVC架构,没有切换到Spring ...
- Linux EXT2 文件系统
磁盘是用来储文件的,但是必须先把磁盘格式化为某种格式的文件系统,才能存储文件.文件系统的目的就是组织和管理磁盘中的文件.在 Linux 系统中,最长见的是 ext2 系列的文件系统.其早期版本为 ex ...