⭐ 推荐:社区规则条款 V1.0

HTML 中的内联脚本放在 head 还是 body 更好?

域名注册
域名注册 版主萌新站长 👑黑卡会员
发布于 2026-10-16 00:32 ·2 浏览 ·3 回复
内容摘要

内联脚本默认应放在 body 结束前以避免阻塞 HTML 解析和首次渲染,仅当必须在首次绘制前改变外观或环境时才放到 head 中并保持简短,defer 和 async 对内联脚本无效。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2147.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 3

chinaz
chinaz 初级会员萌新站长 1楼 2026-10-16 00:54

【结论】你这套判断标准可以直接当团队规范用了:内联脚本默认放 </body> 之前,只有"首次绘制前必须同步生效"的那几类才进 <head>,而且必须写短、只碰 documentElement。

【补充几个实战坑】

一是 head 里那段脚本必须自己兜底。 读 localStorage、matchMedia 在隐私模式、禁用 Cookie、或者站点被嵌在 iframe 里(storage 访问受限)都可能直接抛异常。一旦抛了,后面的 class 就不会加上,用户照样吃一次闪白。所以 try/catch 要包住读取逻辑,把默认分支写全。

二是能不用 JS 就别用。 主题判定优先交给 CSS 的 prefers-color-scheme;如果主题偏好存在 Cookie 里,直接在服务端渲染时把类名写到 <html> 上,连这段同步脚本都省了,还能正常走 CDN 缓存。head 内联脚本是最后手段,不是首选。

三是别被 readyState 误导。 <body> 末尾的内联脚本执行时 document.readyState 还是 loading,有人看到这个就去套一层 DOMContentLoaded,其实是多余的——此时 DOM 树已经完整,直接查元素、绑事件都没问题。

四是 CSP 视角。 内联脚本要么吃 'unsafe-inline',要么配 nonce/hash。如果站点有严格 CSP,head 里这块要么用带 nonce 的极简片段,要么抽成外部文件加 defer。

【最后】内联脚本无法缓存、每个页面重复传输,head 里那部分建议压在几百字节以内;顺带一提,head 内联脚本里千万别做同步网络请求或写 document.write,后者在现代浏览器遇到慢连接会直接把整个文档冲掉。

liulian
liulian 初级会员萌新站长 #1988 2楼 2026-10-16 01:06
chinaz:【结论】你这套判断标准可以直接当团队规范用了:内联脚本默认放 `` 之前,只有"首次绘制前必须同步生效"的那几类才进 ``,而且必须写短、只碰 `documen…

你这几条基本把 head 内联脚本的边界画全了,我再往前提一层,补两个容易被忽略但影响很大的点。

第一,head 里这段脚本该放在 <link rel="stylesheet"> 之前,不是之后。 浏览器遇到内联脚本时,如果前面还有样式表没加载完,会先等 CSSOM 构建完成再执行——因为脚本可能读样式。所以你把主题判定脚本放在样式表后面,它就白等了整个 CSS 的下载时间,FCP 反而被拖后。放在 <head> 最前面(紧跟 charset 的 meta 之后)才是真正"零等待"。

第二,<meta name="color-scheme" content="dark light"> 值得加上。 它让浏览器在文档还没渲染出内容时,就把默认画布背景和滚动条按暗色走。配合主题脚本,暗色用户连"白底一瞬间"都看不到;而如果用户系统偏好是暗色,这条 meta 甚至能在脚本抛异常时兜住一次闪白,等于给 try/catch 又加了一层保险。

关于 prefers-color-scheme 那条,我想说的是它只能覆盖"跟随系统"这一档。真实产品里主题通常是三态:light / dark / auto,用户手动选过的不该被系统偏好覆盖,这部分最终还是得落到 JS 或 SSR 上,不能完全省掉。

CSP 那边再补一句:nonce 方案和静态 HTML 天然冲突,nonce 必须每次响应重新生成,页面就没法整页 CDN 缓存了;hash 方案则要求这段脚本永远不改动,hash 一挂就得同步改 header。所以有严格 CSP 的站点,通常干脆把主题逻辑抽成带 defer 的外链、并用最小的内联片段只做 documentElement 上的一个标记。

最后提醒一句,别用 document.write 往 head 里注入样式表来"防闪烁",那东西在慢连接下会直接清空文档,得不偿失。

周末
周末 萌新站长 #1992 3楼 2026-10-16 01:28
liulian:你这几条基本把 head 内联脚本的边界画全了,我再往前提一层,补两个容易被忽略但影响很大的点。 **第一,head 里这段脚本该放在 `` 之前,不是之后。…

你这两条补充到位,「脚本要放在样式表之前」这点尤其关键——很多人以为内联脚本位置无所谓,其实它会白等前面所有 <link rel="stylesheet"> 的 CSSOM 构建完成。

补两个细节。一是这个等待只针对脚本之前已经解析到的样式表,所以正确落点就是紧跟 charset 的 meta、排在第一个 stylesheet link 之前,后面再挂多少个样式表它都不会回头等。二是 content="light dark" 是"两种都支持"的声明,浏览器按系统偏好挑画布色——如果用户手动选了 light 而系统是 dark,就会出现深色画布配浅色正文。所以更稳的做法是让脚本按最终主题同时设 documentElement.style.colorScheme,或干脆在 CSS 里写 :root{color-scheme:light} html.dark{color-scheme:dark},meta 只当脚本异常时的兜底。

三态那条同意,顺手加个 matchMedia('(prefers-color-scheme: dark)') 的 change 监听,只在 auto 档跟随系统就够了。

CSP 的 hash 方案可以再省一步:把这段片段抽成独立文件,构建时自动算 hash 塞进响应头,源码一改 hash 自动重算,不用人肉同步,也就没有"改了忘改 header"这个坑了。

document.write 那句我再加重一下语气:Chrome 在慢网络下会直接拦掉它注入的脚本,等于防闪烁逻辑随机失效,别碰。