为什么用了defer的JS还是阻塞了首屏渲染?
我在优化网站首屏加载时,把所有JS都加上了defer属性,但页面还是出现卡顿,DOMContentLoaded时间依然有3秒。用Lighthouse检测发现有多个JS文件被标记为”blocking”。
尝试过把JS放在body底部,但第三方统计代码依然显示DOMContentLoaded前就被加载了。查看Network面板发现JS加载时间叠加导致渲染队列变长,像这样:
<script defer src="analytics.js"></script>
<script defer src="app.js"></script>
<script defer src="vendor.js"></script>
明明用了defer,为什么这些脚本还是排在渲染队列里?如何让它们真正异步加载而不阻塞首屏?
问题出在两个地方。第一,虽然你用了defer,但浏览器下载这些脚本时还是会在一定程度上占用主线程资源。特别是当有多个defer脚本时,它们会按顺序下载和执行,这个过程本身就会拖慢渲染。
第二,某些第三方统计代码即使加了defer,内部可能还包含了document.write或者同步XHR这类会强制阻塞渲染的操作。这不是defer能解决的。
推荐的做法是把第三方统计代码改成async方式加载,像这样:
对于自己的业务代码,建议做以下优化:首先合并那些defer脚本,减少HTTP请求数量;其次可以考虑用动态import按需加载非首屏必须的功能。
另外要注意,Lighthouse标记为blocking的脚本,可能是因为它检测到这些脚本在关键渲染路径上产生了影响。你可以通过preload来提前加载关键脚本:
最后提醒下,优化首屏不只是处理JS,还要关注CSS。比如把首屏关键CSS内联,非首屏样式异步加载,这样才能真正提升首屏渲染速度。
要真正实现异步加载且不阻塞首屏,可以将这些非关键JS改为使用async属性,或者通过动态创建script标签的方式加载:
如果你必须保持加载顺序,那可以考虑将多个JS文件合并成一个来减少请求数量,或者使用模块化加载器进行按需加载。