为何body标签下方的脚本会阻塞浏览器渲染?
为什么body下方的脚本会阻塞页面渲染?
原本认为body标签下方的脚本不会阻塞渲染阶段(如DOM树、Render Tree构建及绘制),预期会先快速显示黄色背景,约5秒后JavaScript修改DOM(CSSOM)将背景改为红色。但实际是阻塞5秒后直接显示红色背景,无法先看到黄色背景,请问该脚本为何会阻塞渲染阶段?
代码示例
<style> .section1 { background-color:yellow; width:100wh; height:100vh; } </style> <body> <div class="container"> <section class="section1"> </section> </div> <script> const n = 5000000000 // 在我的设备上大约耗时5秒 for(let i= 0 ; i<n; i++){ } document.querySelector('.section1').style.backgroundColor = "red" </script> </body>
原因解析
浏览器的主线程负责DOM解析、CSSOM构建、JS执行、渲染(布局+绘制)这些核心任务,同一时间只能处理其中一件事,这是导致阻塞的核心原因:
- 虽然脚本放在body末尾,此时DOM树已经构建完成,但浏览器不会立刻触发渲染——它会等待当前同步JS任务执行完毕后,才会进行一次完整的渲染流程。
- 你的脚本里有一个长达5秒的同步循环,这段时间内主线程完全被JS占据,根本没有机会去执行渲染操作。
- 当循环结束后,脚本立刻修改了
.section1的背景色,此时浏览器终于能执行渲染,但渲染树已经包含了修改后的红色背景样式,所以直接显示红色,不会先展示黄色。
简单说:同步JS阻塞了主线程,让浏览器没机会在脚本执行前完成第一次渲染,等到能渲染时,样式已经被改成红色了。
实现预期效果的方法
如果想先显示黄色,再5秒后变红,需要让浏览器在脚本执行前完成一次渲染,比如把长耗时任务放到异步队列里:
<script> // 用setTimeout把任务推到下一个事件循环,让浏览器先完成渲染 setTimeout(() => { const n = 5000000000; for(let i= 0 ; i<n; i++){} document.querySelector('.section1').style.backgroundColor = "red"; }, 0); </script>
更优的方案是用Web Worker处理长耗时计算,完全不阻塞主线程:
// 主线程脚本 <script> const worker = new Worker('worker.js'); worker.postMessage('start'); worker.onmessage = () => { document.querySelector('.section1').style.backgroundColor = "red"; }; </script> // worker.js文件 self.onmessage = () => { const n = 5000000000; for(let i= 0 ; i<n; i++){} self.postMessage('done'); };
内容的提问来源于stack exchange,提问作者minwoo
相关产品推荐
相关产品推荐

