<script> 这个标签看起来特别朴素:不就是引个 JS 吗?

但只要你稍微做过一点性能优化、或者线上被 CDN 劫持/脚本顺序坑过一次,你就会发现:script 的属性不是装饰品,是“加载与执行规则说明书”

下面按“最常用 → 容易踩坑 → 安全相关”的顺序,把属性都捋一遍。

先来一个最常见的 script

1
<script src="https://cdn.example.com/app.js"></script>

默认行为是:

  • 浏览器解析到它会开始下载脚本
  • 下载完会立刻执行
  • 执行期间会阻塞 HTML 解析(你可以理解成:主线程被叫去“先把这段 JS 跑完”)

所以很多属性,本质都是在解决两个问题:

  1. 别卡页面(性能)
  2. 别出事(安全/一致性)

一、决定“下哪里”:src

src

指定外部脚本地址。

1
<script src="/assets/app.js"></script>

注意点:

  • 没有 src 就是内联脚本:<script>...</script>
  • 同一个 src 重复写两次,浏览器未必会傻乎乎执行两次,但也别指望它帮你兜底,工程里最好别这么干

二、决定“怎么解析/怎么跑”:type

type

历史上 type="text/javascript" 很常见,现在多数情况下不写也行(默认就是 JS)。

type 有一个很重要的现代用法:

type=”module”

1
<script type="module" src="/main.js"></script>

它会把脚本当作 ES Module 处理,大体有这些特性:

  • 支持 import/export
  • 默认是延后执行的(行为更像 defer
  • 模块脚本默认是严格模式
  • 同一个模块不会被重复执行(会按模块图做缓存/复用)

你可以把它理解成:“浏览器原生支持的打包/依赖系统”(当然没 bundler 那么全能,但语义是这个方向)。


三、决定“会不会阻塞页面”:async / defer

这俩是最容易被问,也最容易被用错的。

先给一个不那么严谨但很好记的结论:

  • defer:保证顺序,等 HTML 解析完再执行
  • async:谁先下完谁先执行,不保证顺序

defer

1
2
<script src="/a.js" defer></script>
<script src="/b.js" defer></script>
  • 不阻塞 HTML 解析
  • 按标签出现的顺序执行(a 先于 b)
  • 一般会在 DOM 解析完成后、DOMContentLoaded 之前执行

适合:

  • 大多数业务脚本(尤其有依赖顺序的那种)
  • 你想“别卡首屏,但又别乱序”

async

1
2
<script src="/a.js" async></script>
<script src="/b.js" async></script>
  • 不阻塞 HTML 解析
  • 谁先下载完谁先执行(顺序不确定)
  • 执行时仍然会抢主线程(所以如果脚本本身很重,用户照样可能卡一下)

适合:

  • 独立脚本:埋点、监控、广告这类“你不依赖我、我也不依赖你”的

一个常见问题:async 和 defer 能一起写吗?

你可以写,但多数浏览器会按各自规则处理,实际心智会更乱。工程里建议直接选一个:要顺序就 defer,要不管顺序就 async


四、模块脚本的好搭档:nomodule

nomodule

用来做兼容:支持 module 的浏览器跑 type="module",不支持 module 的浏览器跑 nomodule

1
2
<script type="module" src="/main.esm.js"></script>
<script nomodule src="/main.legacy.js"></script>

这套写法的好处是非常直白:现代浏览器吃现代包,老浏览器吃兼容包。


五、安全相关:integrity / crossorigin / nonce / referrerpolicy

这几位属于“平时不显山露水,出事时能救命”的属性。

integrity(SRI,子资源完整性校验)

让浏览器校验脚本内容有没有被篡改(比如 CDN 被污染、传输被劫持)。

1
2
3
4
5
<script
src="https://cdn.example.com/react.min.js"
integrity="sha384-xxx"
crossorigin="anonymous"
></script>

校验不过,浏览器会拒绝执行。

crossorigin

控制跨域请求的凭证与 CORS 行为,常见取值:

  • anonymous:不带 cookie 等凭证
  • use-credentials:会带 cookie(前提是服务端允许)

它经常和 integrity 一起出现。直觉理解就行:你既然要做完整性校验,就得让浏览器用“可校验的方式”去拿资源。

nonce(配合 CSP)

当你开启了 CSP(Content Security Policy),内联脚本通常会被禁掉。nonce 用来做“白名单通行证”。

1
2
3
<script nonce="rAnd0m">
window.__CONFIG__ = { env: 'prod' };
</script>

nonce 一般由服务端动态生成并写入响应头/HTML。核心思路是:只有带了这个 nonce 的脚本才允许执行。

referrerpolicy

控制请求脚本时 Referer 头怎么带,典型用法是减少隐私泄露或避免把路径参数带给第三方。

1
<script src="https://third-party.example.com/a.js" referrerpolicy="no-referrer"></script>

常见取值你不用全背,记住两三个就够用:

  • no-referrer:完全不带
  • origin:只带源(协议 + 域名 + 端口)
  • strict-origin-when-cross-origin:现在很多浏览器的默认行为

六、一些“你可能见过,但别太依赖”的属性

charset(基本算历史遗留)

1
<script src="/a.js" charset="utf-8"></script>

以前用于指定外部脚本编码。现代项目基本统一 UTF-8,这个属性存在感很低。

language(过时)

很早以前用来写 language="javascript",现在不建议用。

id / class / data-*

这些属于通用属性:

  • id/class:给你选中 DOM 用
  • data-*:给脚本传一点配置
1
2
3
4
5
6
<script
src="https://cdn.example.com/analytics.js"
async
data-app-id="xxx"
data-env="prod"
></script>

这种写法很适合第三方 SDK:你不用在全局挂一堆变量,它自己读 dataset 就能初始化。


七、我平时怎么选?(快速建议)

  • 业务主包:优先 defer(既不阻塞解析,又能保证顺序)
  • 第三方独立脚本(监控/埋点):用 async(别挡正事)
  • 想走现代语法与更好的缓存模型type="module" + nomodule 做双产物
  • 上 CDN 的关键依赖:考虑 integrity + crossorigin="anonymous"(别问,问就是吃过亏)
  • 公司安全策略比较严(CSP):准备好 nonce

结尾

script 标签的属性其实就两句话:

  1. 你想让浏览器“什么时候下载、什么时候执行”
  2. 你想让脚本“更安全、更可控”

把这几类属性搞清楚,很多“脚本顺序不对”“页面怎么突然卡一下”“线上怎么样式/初始化没了”的问题,都会少掉一半。

Happy Coding!