引言
近来发现一个浏览器缓存策略影响h5页面调用java script的现象,引发如下问题的验证。
- h5使用 <link rel=“preload” href="{{ js_url }}" as=“script” {{ crossorigin_attr }}>预加载js文件后,为什么浏览器会在script标签处再次发起文件加载。
- 为什么偶现再次加载时可能会在console报错(域名已经在测试环境替换为localhost):Access to script at ‘http://localhost:5001/test.js’ from origin ‘http://localhost:5000’ has been blocked by CORS policy: The ‘Access-Control-Allow-Origin’ header contains multiple values ‘http://localhost:5000,http://example.com’, but only one is allowed.
link element 标签元素的属性rel=“preload”
当link标签的属性rel是preload时,必须包含 as 属性,可选 crossorigin 属性,crossorigin属性的值可为 anonymous 或 use-credentials。
crossorigin属性为空
浏览器发送请求时不携带origin头
服务器有可能不返回Access-Control-Allow-Origin,也可能返回值为多个域
crossorigin="anonymous"(匿名模式)
浏览器发送跨域请求时不携带用户凭证(如 Cookies、HTTP 认证信息、TLS 客户端证书等)。
服务器只需返回 Access-Control-Allow-Origin: *(或具体源)即可,无需返回 Access-Control-Allow-Credentials: true。
crossorigin="use-credentials"(凭证模式)
浏览器发送跨域请求时携带用户凭证(Cookies、HTTP 认证等)
服务器必须返回 Access-Control-Allow-Credentials: true,且Access-Control-Allow-Origin 不能为 *,必须指定具体的源(如 https://your-domain.com)。
script element标签元素的属性
crossorigin属性与link标签的属性值一致,参考上一节说明
关于引言中问题1的实验
第1次触发下载的原因分析
当link标签内容是:<link rel="preload" href="http://localhost:5001/test.js" as="script" >
它没有crossorigin属性,此时浏览器发出预加载请求时不会携带origin头。相应的,服务器有两种场景:
- 不会响应Access-Control-Allow-Origin头
- Access-Control-Allow-Origin头的值不是确定的域或*
由于本服务器配置的是多个域,那么他会直接响应:Access-Control-Allow-Origin: http://localhost:5000,http://example.com
注:这是符合协议规范的

再来看第2次资源下载的原因分析
参考网络资料的说明:由于在script标签设定了crossorigin属性,因为 <link rel="preload"> 和 <script> 标签的 crossorigin 属性不一致,导致浏览器无法复用缓存,从而发起两次网络请求。
<script src="http://localhost:5001/test.js" crossorigin="anonymous"></script>
关于引言中问题2的实验(CORS 错误)
第2次 test.js 资源下载请求,实际没有发出网络请求,是命中了浏览器缓存
那么既然在h5页面中 link、script标签的crossorigin属性不一致时,会认为时不一样的资源,为什么浏览器实际还是命中缓存了,参考如下
实际上这次并没有真的发出网络请求,由于第一次下载响应了Cache-Control: max-age=10浏览器命中了缓存文件,证据如下

服务端日志,这个时段只收到1次下载请求

参考网络资料得出:
虽然 Fetch 规范建议根据请求的凭证模式(credentials mode)区分缓存条目,但实际浏览器实现中,如果服务器响应允许公开缓存(public),并且没有依赖 Cookie 或 Origin 的差异,缓存条目可以被不同凭证模式的请求复用。这属于一种优化行为,但并非所有浏览器都一致,也可能受到具体版本的影响。
即受Cache-Control响应头的影响,验证测试响应为no-cache时,第二次加载(script加载)无法命中缓存,且由于有crossorigin="anonymous"属性,可以正常拿到资源,且无跨域报错

那为什么说是偶现的呢?
在浏览器devtools的network调试页面中,通常会勾选disable cache,就模拟出了响应头Cache-Control: no-cache的效果
正确的预加载方案
在link和script标签中设定相同的跨域属性,既能保证正常跨域,又能减少资源加载次数、节省带宽
<link rel="preload" href="http://localhost:5001/test.js" as="script" crossorigin="anonymous">
<script src="http://localhost:5001/test.js" crossorigin="anonymous"></script>
