debounce/防抖:抖完以后在执行
等待-执行
throttle/节流:按计划"节制"执行(我会直接翻译成节制)
执行-等待
debounce
想象一下,使用单反或者手机拍照,总会有一个对焦的过程,举个冷笑话,如果你的"帕金森"正好发作,如何才能拍出好照片?很简单啊,病好了再拍,这就是防抖的基础原理
想象一下,我们跑步的时候看东西(肯定没啥问题),但如果运动+摄像,那画面应该很难接受,为什么用眼直接看没有问题呢?因为眼球会根据情况进行抖动,简称共振,大脑会根据当时的情况进行处理和分析,简称脑补
如果大脑不够用怎么办?软件不够硬件凑,"鸡头防抖"尝试下
如果没有"鸡头"呢?青蛙的动态视觉,蝙蝠的超声波,苍蝇的复眼
.....
防抖是光学处理上必不可少的处理,debounce
的中文翻译即防抖,毫无疑问这里引用的是防抖的概念,这种跨行引用,本身也没有严格的规范与描述或者唯一的解决方案
吐槽1
针对光学防抖,还有补抖的概念,所以我对防抖的理解依然处于达到用户预期结果,而优化性能,只能说是顺带
吐槽2
以下说的防抖解决方案,可以直接理解为高频或ajax重复提交解决方案,通用的部分被称为debounce/throttle,但在某些特殊情况下又会有些变种
throttle -> requestAnimationFrame
控制自己,注意节制
所谓的节制即单位时间(n秒)内,最多做有限次(m次)
节制对于产品需求更为常见
比如各种api访问限制,1小时只可访问3000条,1天不可超过10w次等等,很显然这些都是后端处理
对于前端来讲,使用节制会产生"副作用",即后续请求可能会拒绝执行,除了产品需求,他被使用的地方非常有限,仅限于用户"被动"提交的高频交互
- 产品需求
做个一分钟点击某处次数最多或者打鼹鼠的游戏,如果用户没有操作节制,按键精灵第一 - scroll
滚动加载(也可以防抖解决,效果不如节流好) - mousemove
反向例子,真用到mousemove的话,比如轨迹划线,只会认为mousemove自带节制,使轨迹不连贯,还需要特殊处理 - 卡顿操作
常见动画,canvas,图标等需求,描述为卡顿,专业点叫丢帧,原因是reflow与repaint
毕竟像内存溢出,如
for(var i=0,arr = [];i<3600*1000*1000;i++)arr.push(i)
或复杂逻辑,如
var now = Date.now();while(Date.now() - now < 3*1000){}console.log(1);
正常是不会存在前端的
而所谓的卡顿,即函数每次执行时间超过1000/fps,一般出现情况是一帧内多次执行(高频)某函数引起了渲染超过16ms(默认),而对于某函数就是需要16ms,做优化重写吧
而在下一帧执行函数即requestAnimationFrame
,所以throttle的使用并不多见
1.抖是什么
由用户在结果返回前产生的非预期操对结果产生影响的因素
以上为模仿官方的描述,对于前端主要针对ajax(spa)其实就是
- 快速点击(spa/get)
用户原因:
服务器反应慢误判
单击与双击的惯性操作
客户反悔快速切换频道
.....
后端原因:
服务器慢
后台为了性能,不提供分页描述,只提供内容数据(老观念)
......
注:
如果是后端渲染的get,会直接修改url,无论多慢,都是以最后一次操作为准,绝对不会有显示预期问题
- 重复提交(post)
用户原因:
同get
后端原因:
没有做重复校验(依赖前端校验)
数据库没有唯一约束(正好赶上数据第一条添加中,第二条通过了)
......
- 其他高频触发事件
略,一般会有明显的需求描述
2.解决方案
快速点击(spa/get)
以搜索框提示为例,根据输入内容获取相关索引,如果不做处理,在"帕金森"发作时,同一时间多次提交后台,可能会引起内容与关键字比匹配的bug
1.防抖 - 抖完在执行
通用解决方案,即n秒内只执行1次,且只执行最后一次,即抖完
如淘宝搜索框
对后台的请求只执行了两条
实现关键点
- setTimeout
- clearTimeout
注:
这是最通用的方法,实际上他并没有真正的解决问题,只能降低概率,能提的也只有减少请求次数
比如当第一次请求完成时间400ms,第二次请求为40ms,debounce设置30ms,中间间隔10ms,那显然,第二次请求在第一次请求完成前就已经完成,例子中的问题依然没有解决
2.防抖 - 抖动即取消
实际上,异步防抖,必须将前一个未完成的请求中断,即抖动即取消,也可以这么说
如果函数不可以取消,抖完了在执行
如果函数可以取消,抖动即取消(上一个未完成的函数)
丢张度娘的搜索,理解下
异步处理(多线程,事务,原子操作)怎么理解都行,或许他并不是通常的防抖,不过在ajax这种例子里,1,2是要混用的
实现关键点
- abort
注:
如果把防抖的时间,设置为ajax执行完成,那他就变成了节流
3.节制 - 缓存
已查询过的索引是否也可以缓存?按需求来,get幂等(一般可以缓存)
节制 + 缓存
即如果在节制期内,查询缓存
实现关键
- memoize
重复提交(post)
以新增表单为例,对没有处理的新增按钮多次点击,可能会引起后台多条数据的添加
4.节制 - 单例
表单提交期间,button[disabled]或全局转菊花
节制 + 上一个未完成
这是一种特殊的节流,依然可以理解为事务操作,在前一个操作未反馈前,不应该在发送请求
当然,也可以这么理解,把throttle的时间参数转换为函数
批量加载
首次页面打开,需要多个ajax初始化,如何优化
5.防抖 - 合并
单位时间内请求的ajax进行合并,而后通过超级接口获取数据
合并才是防抖的精髓,相机如何自动防抖?在用户点击执行按钮时,连续拍摄多张,而后进行合成即可,人眼也是(脑补)
完结
对我来讲,第一次使用debounce/throttle,是在某工具类(lodash/underscore或其他系列_)中,没有中文翻译,直接正面用,翻译直接是防反跳/节流阀,也叫等待-执行或执行-等待,引入的例子是电梯,就知识点将他归为高频处理/重复提交,不知道是哪路神仙大拿的经典翻译,果然十分形象,这里完全是以防抖与节流的名义,描述如何处理常见的ajax问题
在某些早期库中,会用一个函数封装debounce/throttle,并在最后传递boolean以确定是先执行还是后执行,这就是为什么我会习惯称debounce为等待-执行,throttle为执行-等待