首屏白屏或延迟主因是script默认阻塞html解析——浏览器流式解析遇无defer/async的script即暂停dom构建、样式计算与绘制,哪怕仅console.log(1)在head中也会导致body内容无法解析。

script 标签位置不对,首屏白屏或延迟不是代码写得差,而是浏览器解析机制被卡死——跟 JS 体积、语法是否优雅完全无关。
为什么 <script></script> 放在 会直接白屏
浏览器解析 HTML 是单线程流式过程,遇到没加 defer 或 async 的 <script></script>,立刻暂停 DOM 构建、样式计算和页面绘制。哪怕只有一行 console.log(1),只要它在 里, 内容就压根不会开始解析。
- 常见现象:
document.getElementById("app")返回null,不是元素漏写了,是解析根本没走到那儿 - 文字写在
<script>alert(1)</script>前面,用户却先弹窗后见字——DOM 已插入,但渲染被冻结 - Network 面板里脚本下载耗时仅 120ms,但
domInteractive却晚了 800ms——同步加载拖垮整个关键路径 - 内联脚本(无
src)放里必须确保不操作任何 DOM,否则必报错
<script></script> 放在 前为什么能安全操作 DOM
此时 HTML 解析已基本完成,document.body 和绝大多数首屏元素都已就绪,调用 getElementById、绑定事件、初始化 Vue/React 实例基本不会出错。
- 性能上:页面结构优先渲染,用户感知更快;脚本加载执行延迟对首屏无感
- 注意点:别写成
<script></script>——虽然浏览器会自动“塞回”,但不符合规范,CI 或 lint 可能报 warning - 多个脚本按依赖顺序从上到下排列,比如
utils.js必须在app.js前,就依次写在 前 - 若某个脚本必须提前加载(如 Promise Polyfill),应放
+defer,而不是硬塞进底部再用setTimeout等 DOM
defer 和 async 不是万能解药,用错照样卡住首屏
defer 只对外链脚本(带 src)生效,内联脚本加了也无效;async 不保序,且可能在 DOM 尚未就绪时执行。
-
defer脚本并行下载、按 HTML 顺序执行,但必须等整个 HTML 解析完才运行——适合依赖完整 DOM 的逻辑(如导航菜单初始化),但若某个defer脚本加载极慢,DOMContentLoaded就会被拖住 -
async适合纯独立逻辑(如统计 SDK),谁先下完谁先执行;但若它尝试绑document.getElementById("btn").addEventListener,大概率报Cannot read property 'addEventListener' of null -
type="module"默认行为等价于defer,且支持import,现代项目可优先用它替代传统外链 - 别信“
DOMContentLoaded比load快”的模糊说法——实测晚 100ms 加载,LCP 就晚 100ms
首屏内容靠后、DOM 嵌套过深会放大 script 位置问题
script 位置只是表象,真正让首屏慢的,是它和 HTML 结构共同构成的关键渲染路径(CRP)被拉长。
- 首屏核心内容(主标题、首图、关键表单)如果写在 HTML 底部,哪怕体积再小,也得等前面所有标签解析完才进入 DOM 树,LCP 自然被拉长
- DOM 深度超 6 层(可用 DevTools → Elements → 右键节点 →
Show DOM properties查看),低端机上 FCP 可延迟超 200ms;<table> 里嵌 <code><div> 更危险,易触发同步 Layout <li>内联 <code><style></style>或<script></script>超 14KB,不仅拉高 TTFB,还会让解析器卡在文本扫描阶段;关键资源(如字体、首屏图)没加preload或preconnect,就得等 HTML 解析到对应标签才发起请求
真正要调的不是“脚本放哪儿”,而是整个 HTML 流式解析过程中,哪一环被人为打断、哪一层嵌套在重复做无谓计算、哪些资源本该提前告诉浏览器——这些细节不抠清楚,光挪个 <script></script> 标签位置,解决不了问题。











