「这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战」。
之前看防抖和节流的时候,看到短时间内大量的事件触发会引起浏览器卡死,浪费浏览器性能,那么为什么事件触发会引起阻塞页面的情况呢?引起页面阻塞的原因真的是因为事件触发太多了吗?
注:本篇文章测试用例来源于 Chrome
怎样算卡?
页面运行阶段怎样算是卡?如果以用户的角度来看,那点击一个按钮没响应,算卡;下拉页面没动,算卡;图片停止加载,GIF 图不转了,动画停止了,算卡;总之,就是页面无法做出用户认为正确的动作就算卡,此时在本篇文章中认为是页面被阻塞,那么事件的高强度触发会不会导致页面的卡呢?
交互失效
关于浏览器运行这块, JS 线程是和 GUI 渲染线程是互斥,我们通常说的页面卡死,就是不动了,没响应了,通常可能就是 JS 线程在高强度占用中,而为什么说,事件高频触发会影响到浏览器卡死,是因为 Event Loop 机制,事件触发后会往任务队列中的塞入任务,就是像点击事件的回调这种,然后 JS 引擎会读取任务队列,然后执行 JS, 这个时候如果说你的 JS 执行时间很久,那么就会阻塞页面的渲染,那么高频触发的话就会一直阻塞页面,那么为了证明上面的猜想(目前是),就去试试看
首先验证,点击按钮没响应这块,这方面对应的是 JS 无法及时执行,监听 mouseenter 事件(因为点击事件在动图不太能看出来),和以一个空转的 for 循环来模拟 JS 高强度运算的情况,代码如下
<!DOCTYPE html>
<html>
<body>
<div onmouseenter="add()"></div>
</body>
<script>
// 9 个 0
// 点击一次会打印一次
let num = 0;
function add() {
console.log(++num);
for (let i = 0; i < 3000000000; i++) {}
}
</script>
<style>
div {
width: 100px;
height: 100px;
background-color: black;
}
</style>
</html>
结果如下
mouseenter 事件触发了三次,就是鼠标移进去 div 3 次,第一次马上打印出来了,第二次等了一下才打印,这说明 JS 的执行的确会阻塞页面,但是,这是交互上的阻塞,导致 JS 延迟执行,因为 JS 线程在第二次事件触发时还在执行第一次的 JS, 注意,有小细节,这里提到了 线程,其实刚才页面上还有一个线程,那就是事件触发线程,从刚才一个打印了 3 次而不是 1 次或者两次,可以说明事件触发线程它是一个独立的线程,类似下载线程,不是和 JS 线程互斥的,当我们 JS 线程在执行的时候,事件也能够正常的被触发
这里也回一下主题,就是事件高频触发会不会阻塞页面,从这种情况来看,因为事件触发线程的独立,所以 JS 线程会不断执行被任务队列中的任务,如果恰巧这个任务是计算复杂型的 JS, 要花很长时间,比如点击一个按钮后没响应再多次点击,就会造成 JS 延迟执行,最终导致整个页面的交互失效
这种情况可以考虑使用 web worker 再开一个线程的方式去处理,感兴趣的去百度
动画类阻塞
JS 延迟执行算是交互类型的一种阻塞,那么 JS 线程是和 GUI 线程互斥, 事件的高频率触发,也必然会不断执行 JS, 那么类似 GIF 和 动画这种涉及 GUI 线程的会不会被阻塞,也就是证明 JS 线程和 GUI 线程互斥,还是以上面的例子为模板,加上一个 img 动图
<!-- ... -->
<div onmouseenter="add()"></div>
<p>---分割线---</p>
<img src="http://localhost:8000/gif.gif" />
<!-- ... -->
结果如下
这个结果我还是有点意外的,因为我之前看过一篇文章曾经说过 JS 执行会阻塞动图的加载,现在实践看来并不是真的,难道是我们用的浏览器不一样?至少 Chrome 的计算复杂型的 JS 并不会阻塞动图的加载
那么排除掉事件触发对动图加载的影响,接下来将看一下 CSS 动画会不会受到 JS 的影响,还是以上面的例子为模板,加上一个从左到右的动画
<!-- ... -->
<div onmouseenter="add()"></div>
<p>---分割线---</p>
<div class="animation"></div>
<!-- ... -->
<style>
/* ... */
@keyframes move {
from {
left: 0;
}
to {
left: 100%;
}
}
.animation {
position: absolute;
animation: move 5s linear infinite;
}
</style>
还是熟悉的 3 次 mouseenter 事件,我们设计的动画最终会等待 JS 的执行完成才能够继续进行,因此我们可以得出结论,在页面存在 CSS 动画的情况下,如果事件的高频触发并且触发的回调是计算复杂型的 JS, 会导致页面的阻塞(动画无法进行)
总结
- 事件的频繁触发并不是导致页面阻塞的真正原因,因为事件触发线程是独立线程
- 事件触发的回调的 JS 是否是
计算复杂型,会不会是一个长任务才是阻塞页面的真正原因