为什么用了defer的JS还是阻塞了首屏渲染?

慧芳的笔记 阅读 41

我在优化网站首屏加载时,把所有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,为什么这些脚本还是排在渲染队列里?如何让它们真正异步加载而不阻塞首屏?

我来解答 赞 10 收藏
二维码
手机扫码查看
2 条解答
迷人的玉鑫
defer属性确实会让脚本延迟到文档解析完成后才执行,但这里有个常见的误区。根据HTML规范,defer只保证脚本的执行顺序在DOMContentLoaded事件之前,而不是说完全不阻塞渲染。

问题出在两个地方。第一,虽然你用了defer,但浏览器下载这些脚本时还是会在一定程度上占用主线程资源。特别是当有多个defer脚本时,它们会按顺序下载和执行,这个过程本身就会拖慢渲染。

第二,某些第三方统计代码即使加了defer,内部可能还包含了document.write或者同步XHR这类会强制阻塞渲染的操作。这不是defer能解决的。

推荐的做法是把第三方统计代码改成async方式加载,像这样:
<script async src="analytics.js"></script>


对于自己的业务代码,建议做以下优化:首先合并那些defer脚本,减少HTTP请求数量;其次可以考虑用动态import按需加载非首屏必须的功能。

另外要注意,Lighthouse标记为blocking的脚本,可能是因为它检测到这些脚本在关键渲染路径上产生了影响。你可以通过preload来提前加载关键脚本:
<link rel="preload" href="critical.js" as="script">


最后提醒下,优化首屏不只是处理JS,还要关注CSS。比如把首屏关键CSS内联,非首屏样式异步加载,这样才能真正提升首屏渲染速度。
点赞
2026-02-18 14:05
一苗 ☘︎
问题在于defer属性虽然让脚本加载不阻塞HTML解析,但脚本执行时依然会阻塞渲染。所有带defer的脚本会在HTML解析完成后按顺序执行,这就导致了执行阶段依然会阻塞首屏渲染。

要真正实现异步加载且不阻塞首屏,可以将这些非关键JS改为使用async属性,或者通过动态创建script标签的方式加载:

function loadScript(src) {
const script = document.createElement('script');
script.src = src;
script.async = true;
document.head.appendChild(script);
}

loadScript('analytics.js');
loadScript('app.js');
loadScript('vendor.js');


如果你必须保持加载顺序,那可以考虑将多个JS文件合并成一个来减少请求数量,或者使用模块化加载器进行按需加载。
点赞 7
2026-02-08 12:03