放置在body末尾的script标签添加defer属性是否多余?
示例代码参考
第一种写法(body末尾脚本带defer属性):
<body> ...所有内容均位于脚本上方... <script src="https://foo/bar.js" defer></script> </body>
第二种写法(body末尾脚本无defer属性):
<body> ...所有内容均位于脚本上方... <script src="https://foo/bar.js"></script> </body>
核心结论
两者效果并不完全一致,直接移除defer属性在特定场景下会产生可观测的差异,不能直接等价替换。
两者确实存在共性:两种写法都能保证脚本执行时,标签上方的所有DOM节点已经完成解析,不会出现“获取不到前置DOM元素”的问题,这也是很多开发者误以为二者完全等价的核心原因。
具体差异主要体现在三个方面:
- 资源下载时机不同
不带defer的普通末尾脚本:浏览器必须解析到这个script标签的位置,才会发起脚本的网络请求,请求完成后立刻执行,执行过程会阻塞主线程。
带defer的脚本:浏览器在HTML解析的早期阶段,就会通过预加载扫描器识别到带defer的外部资源,提前发起网络下载,不需要等解析到标签位置才开始加载,在弱网、脚本体积较大的场景下,能明显缩短资源加载完成的等待时间,让页面更早达到可交互状态。 - 执行时机与事件触发逻辑不同
不带defer的普通脚本:下载完成后会立刻中断当前的HTML解析流程执行脚本,等脚本执行完毕才会继续后续解析,全部解析完成后触发DOMContentLoaded事件。哪怕脚本放在body末尾、后面只剩</body>闭合标签,这个阻断逻辑依然存在。
带defer的脚本:整个下载过程完全不阻断HTML解析,执行时机固定在「全文档解析完成、DOMContentLoaded事件触发之前」,如果页面存在多个带defer的脚本,会严格按照标签在文档中出现的顺序依次执行。 - 部分API行为存在本质差异
最典型的是document.write():放在body末尾的普通同步脚本里调用document.write(),会把内容正常写入到脚本所在的文档位置;但defer脚本执行时文档解析已经结束,此时调用document.write()会隐式触发document.open(),直接清空整个已加载的页面内容,很容易引发线上故障。
实际使用建议
如果只是实现“操作脚本前置DOM”的基础需求,两种写法在简单场景下表现相近,但二者从加载性能到执行逻辑都有明确区别,不能直接划等号。如果想要更优的加载性能,哪怕脚本放在body末尾,加上defer依然有实际收益;但如果脚本依赖同步写入文档这类逻辑,就不能随意加defer属性。
内容的提问来源于stack exchange,提问作者Joji
相关产品推荐
相关产品推荐

