背景知识之事件循环
详见HTML规范
这里只挑与本文相关的讲
在事件循环中定义了很多任务源,比如鼠标键盘等输入操作的用户交互任务源
一次点击操作,其实包含多个输入操作(mousedown,mouseup,click),都添加到相同的任务源队列中,进而产生多轮的事件循环,其表现就是执行相关元素的事件回调(宏任务)
宏任务执行后,后面就是微任务队列和更新渲染阶段
每轮事件循环可能是非常快的,每秒执行事件循环的次数可能大于60次
受硬件刷新率影响,我们只要保证 fps 达到最大硬件刷新率(比如60)即可,因此不需要每轮事件循环都更新渲染
视图渲染的时机又是什么时候呢?update rendering 发生在本轮事件循环的 microtask 队列被执行完之后,也就是说执行任务的耗时会影响视图渲染的时机。通常浏览器以每秒 60 帧(60fps)的速率刷新页面,据说这个帧率最适合人眼交互,大概16.7ms 渲染一帧,所以如果要让用户觉得顺畅,单个 macrotask 及它相关的所有 microtask 最好能在16.7ms 内完成。但也不是每轮事件循环都会执行视图更新,浏览器有自己的优化策略,例如把几次的视图更新累积到一起重绘,重绘之前会通知 requestAnimationFrame 执行回调函数,也就是说 requestAnimationFrame 回调的执行时机是在一次或多次事件循环的UI render阶段。
最后的总结:
1、每轮事件循环分为3个步骤: a) 执行 macrotask 队列的一个任务
b) 执行完当前 microtask 队列的所有任务
c) UI render
2、浏览器只保证 requestAnimationFrame 的回调在重绘之前执行,没有确定的时间,何时重绘由浏览器决定
输入事件与滚动事件的执行时机
对于输入事件,其执行时机为每轮事件循环的任务执行阶段,这个事件是不受刷新率影响的,每秒的执行次数可能多于60次
为什么谈这个呢,因为这个与滚动事件(scroll)回调的执行时机不一致
按照 HTML 规范,滚动事件回调在 UI Render 阶段的某个步骤中进行,而不是单独的一个任务源
也就是说,滚动事件回调受渲染时机影响,仅执行更新渲染时才执行该回调。
换句话说,该事件自带节流。
举个例子验证下输入事件和更新渲染的执行时机
document.addEventListener("mousemove",function(){
let start = performance.now()
console.log("mousemove:",start)
requestAnimationFrame(function(t){console.log("ui render:",start,t)})
})
// 结果就是可能输出几轮 mousemove 然后执行一次 ui render -- 清空 rAF 回调队列(输出多次 ui render)
/*
mousemove: 4091.025000088848
ui render: 4091.025000088848 4077.594
mousemove: 4098.845000029542
ui render: 4098.845000029542 4094.278
mousemove: 4110.160000040196
mousemove: 4115.5349999899045
ui render: 4110.160000040196 4110.962
ui render: 4115.5349999899045 4110.962
mousemove: 4123.810000019148
mousemove: 4130.160000058822
ui render: 4123.810000019148 4127.719
ui render: 4130.160000058822 4127.719
*/
说明更新渲染有一定的间隔,至少是 1/60 的间隔,而输入任务没有此限制
所以,以下代码是没有效果的,因为该回调已经自带节流了.
document.addEventListener("scroll",function(e){
requestAnimationFrame(function(t){
//执行scroll具体逻辑
})
})
什么时候滚动需要做节流
利用节流可以减少回调的执行次数,使得固定时间周期内只执行一次
刚才提到,滚动事件自带节流,节流的时间周期是与渲染时机相关
判断是否需要额外的节流的关键是:当前的节流规则,是否大部分回调的执行都能让用户受益
如果是动画效果,实时绘制的界面等,则不需要额外的节流了。
以长列表为例,每次执行滚动回调,会计算新的渲染列表项及滚动偏移位置。如果应用更大时间周期的节流,会出现某一帧出现滚动但界面没有更新的情况,让用户感觉产生卡顿。
而其他比较复杂的业务逻辑,不能在短时间内得到反馈的,则需要额外进行节流
以滚动懒加载图片为例
由于每秒的滚动回调的执行次数可能达到60次,而每次执行都需要去获取当前处于视区的占位图并发起图片请求
而这大部分回调的执行,用户是不能受益的,所以我们可以提高节流的时间周期,比如 500ms 这样
结论
滚动事件已自带节流,只有一些特定的业务逻辑才需要额外进行更高时间周期的节流