1. 元素
HTML 中的 <script>元素是网页添加动态行为和交互功能的主要工具,<script>允许嵌入或引用JavaScript代码。
1.1 元素的用法
使用<script>的方式有两种:通过它直接在网页中嵌入JavaScript代码,以及通过它在网页中包含外部JavaScript文件。
1. 内联脚本:将 JavaScript 代码直接写在 <script>标签内。
<script>中的代码会从上到下解释,在<script>元素中的代码被计算完成之前,页面的其余内容不会被加载,也不会被显示。
<script>
function sayScript() {
console.log("script");
}
</script>
2. 外部脚本:通过 src属性引用外部的 .js文件。
与解释行内代码一样,在解释外部 JavaScript 文件时,页面也会阻塞,阻塞时间也包含下载文件的时间。
<script src="example.js"></script>
使用了 src 属性的 <script>元素不应该在 <script>和标签中再包含其他 JavaScript 代码。
如果行内代码和src属性同时存在,浏览器会忽略行内代码,只下载并执行src属性中的脚本文件。
1.2 元素属性
| 属性 | 说明 |
|---|---|
| src | 指定外部脚本文件的URL。 |
| type | 指定脚本语言的内容类型(MIME类型)。通常是"text/javascript"(HTML5中可省略)。如果值为modele,代码会被当成ES6模块处理,这时代码中可允许使用import和export关键字。 |
| async | 异步加载(只对外部脚本有效) 。浏览器会立即开始下载脚本,但不会阻止其他页面动作,比如下载资源或等待其他脚本加载,即并行下载脚本,下载完成后立即暂停HTML解析并执行该脚本。多个async脚本不保证能按照它们出现的次序执行。 |
| defer | 延迟执行(只对外部脚本有效), 最好只包含一个defer脚本。表示浏览器会并行下载脚本,defer脚本会延迟到HTML文档完全被解析和显示之后再按顺序执行,通常在DOMContentLoaded事件之前执行 ****。 |
| integrity | 子资源完整性(SRI)校验。 比对接收到的资源的哈希值和指定的加密签名是否匹配,如果不匹配,则页面会报错,脚本不会执行。这个属性可以用于确保内容分发网络(CDN)不会被篡改而提供恶意内容。 |
| crossorigin | 配置相关跨域请求的CORS(跨源资源共享)设置,经常用于处理跨域脚本的错误日志记录。默认不使用CORS。属性值"anonymous" 配置文件请求不必设置凭据标志。属性值“use-credentials"设置凭据标志,意味着出站请求会包含凭据。 |
| nomodule | 布尔值,用于在不支持ES6模块的旧浏览器中提供回退脚本。 |
1.2.1 defer 推迟执行脚本
**defer**属性表示脚本会被延迟到整个页面都解析完毕后再运行。因此,在<script>元素上设置 defer 属性,相当于告诉浏览器立即下载,但延迟执行。
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script defer src="example1.js"></script>
<script defer src="example2.js"></script>
</head>
<body>
<!-- 这里是页面内容 -->
</body>
</html>
虽然HTML5规范中要求脚本按顺序执行,但实际上,推迟执行的脚本不一定按顺序执行或在DOMContentLoaded事件之后执行,所以建议最好只对一个脚本设置**defer**属性。
defer 属性只对外部脚本文件才有效。这是 HTML5 中明确规定的,因此支持 HTML5 的浏览器会忽略行内脚本的 defer 属性。
早期的浏览器可能不支持defer属性, 还是把要推迟执行的脚本放在页面底部比较好。
1.2.2 async 异步执行脚本
从改变脚本处理方式上看,async 属性与 defer 类似。同样,async属性也只适用于外部脚本,它们都会告诉浏览器立即开始下载。不过,不同的是, async属性并不保证能按照它们出现的次序执行,所以重点在于它们之间没有依赖关系。
异步脚本保证会在页面的 load 事件前执行,但可能会在 DOMContentLoaded之前或之后。
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script async src="example1.js"></script>
<script async src="example2.js"></script>
</head>
<body>
<!-- 这里是页面内容 -->
</body>
</html>
给脚本添加 async 属性的目的是,告诉浏览器不必等脚本下载和执行完后再加载页面,同样也不必等到异步脚本下载和执行后再加载其他脚本。因此异步脚本不应该在加载期间修改 DOM。
1.3 动态加载脚本
使用DOM API,通过向DOM中动态添加script元素同样可以加载指定的脚本。以这种方式创建的
let script = document.createElement('script');
script.src = 'gibberish.js';
script.async = false; //不是所有浏览器都支持 async 属性。因此,如果要统一动态脚本的加载行为,可以明确将其设置为同步加载
document.head.appendChild(script);
以这种方式获取的资源对浏览器预加载器是不可见的,这会严重影响它们在资源获取队列中的优先级,可能会严重影响性能。要想让预加载器知道这些动态请求文件的存在,可以在文档头部显式声明它们:
<link rel="preload" href="gibberish.js">
注意事项:
1. 优先异步加载:使用 async 或 defer 属性避免阻塞渲染。
script.async = true; // 异步加载,不阻塞页面
2. 错误处理与重试:监听 onerror 事件并实现重试机制:
script.onerror = () => {
console.error('加载失败,尝试重试...');
setTimeout(() => loadScript(url), 2000);
};
3. 缓存策略:添加时间戳或版本号避免缓存旧脚本:
script.src = `${url}?v=${Date.now()}`;
4. 结合现代工具链: 使用 Webpack 的动态导入(Dynamic Import)或 ES Modules 实现标准化动态加载
import('module.js').then(module => {
module.init();
});
-
推荐使用场景:按需加载、模块化架构、性能敏感型页面。
-
不推荐场景:简单页面、无明确性能需求的小型项目。
-
关键权衡:在灵活性与维护成本之间取得平衡,优先使用框架(如 Vue/React)的官方动态加载方案
1.4 浏览器如何加载
浏览器会按照<script>在页面中出现的顺序依次解释它们,前提是它们没有使用 defer 和 async 属性。第二个<script>元素的代码必须在第一个<script>元素的代码解释完毕才能开始解释,第三个则必须等第二个解释完,以此类推。
- 解析 HTML → 遇到
<script>→ 暂停解析。 - 下载脚本 → 执行脚本 → 恢复解析。
- 重复上述过程,直到所有同步脚本处理完毕。
- HTML 完全解析后触发
DOMContentLoaded。
| 脚本类型 | 加载行为 | 执行时机 | 对 DOMContentLoaded 的影响 |
|---|---|---|---|
| 同步脚本 | 阻塞解析,顺序加载执行 | 立即执行 | 必须等待所有同步脚本执行完成才触发 |
| async 脚本 | 异步加载,不阻塞解析 | 下载完成后立即执行(无序) | 不阻塞触发,但可能在事件前后执行 |
| defer 脚本 | 异步加载,不阻塞解析 | DOM 解析完成后按顺序执行 | 等待其执行完成才触发(但比同步脚本更晚) |
扩展: load 事件与 DOMContentLoaded 的区别?
页面执行顺序:DOM 树解析完成->DOMContentLoaded->所有资源(图片、js、css等)加载完成-> load``。 可以发现,**DOMContentLoaded更早,而load**更晚。
从性能优化角度建议:
- 优先使用
DOMContentLoaded: 尽早执行 DOM 操作(比如表单验证、动态绑定事件等),减少用户等待时间。 - 避免同步脚本阻塞解析 :和
<style>不同,<script>默认阻塞DOM的解析,所以使用async或defer加载脚本,能减少对DOMContentLoaded的延迟。 - 按需使用
load:仅在必须操作完整资源时使用,避免不必要的性能损耗。
// 监听 DOMContentLoaded
document.addEventListener("DOMContentLoaded", () => {
console.log("DOM 已就绪,可操作元素");
});
// 监听 load
window.addEventListener("load", () => {
console.log("所有资源加载完成");
});
1.5标签位置
过去,所有的<scirpt>元素都放在页面的<head>标签中,目的是把外部CSS文件和JavaScript文件集中在一起。
JavaScript解析阻塞资源,会暂停 DOM 构建和渲染。上面这种做法也就意味着必须等到所有 JavaScript代码都下载、解析和解释完成之后,才能开始渲染(浏览器解析到<body>标签时开始渲染)页面,这就会导致页面渲染明显延迟,这个过程中浏览器窗口会处于空白。
所以,现代Web应用程序通常会把所有JavaScript引用放在<body>元素内容的最后面以减少阻塞。这样,页面会在处理JavaScript代码前完成渲染,用户会感觉页面加载的更快。
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
</head>
<body>
<!-- 这里是页面内容 -->
<script src="example1.js"></script>
<script src="example2.js"></script>
</body>
</html>
扩展:
CSS
的位置?
页面资源的加载顺序:HTML → CSS(头部) → DOM 构建 → Script(底部或异步)。
与JavaScrip不同,CSS 是渲染阻塞资源,但不会阻塞 DOM 树构建(仅阻塞渲染树生成)。浏览器需要等到 CSS 解析完成才能绘制页面,所以需要尽早加载。
因此,把CSS放在中是最推荐的做法。
- 避免渲染阻塞:浏览器在解析 HTML 时优先加载 CSS,确保页面内容首次渲染(FCP)时已应用正确样式,避免布局抖动(Layout Shift)。
- 并行加载优化:现代浏览器支持并行下载 CSS 和解析 DOM,减少整体加载时间。
- 样式优先级控制:
<head>中的 CSS 优先级高于<body>中的内联样式,确保样式一致性。
这里有一个误区,会认为“将 <style> 放在 .vue 文件底部会导致样式加载延迟”。
在 .vue 文件中,构建工具会提取所有样式并注入 <head>,因此最终加载顺序与文件内位置无关,建议按标准的代码顺序:<template> -><script> -> <style>。
1.6 行内代码与外部文件
尽可能把 JavaScript 代码放在外部文件。推荐使用外部文件主要有2个理由:
-
可维护性。JavaScript 代码如果分散到很多页面,会导致维护困难。而用一个目录保存所有 JavaScript 文件,更容易维护,这样可以独立于 HTML页面来编辑代码。
-
缓存。
2.元素
针对早期浏览器不支持JavaScript的问题,<noscript>元素被用来给不支持JavaScript的浏览器提供替代内容。
虽然现在的浏览器已经100%支持JavaScript,但对于禁用JavaScript的浏览器来说,<noscript>仍然有它的用处。
<noscript>元素可以包含任何可以出现在中的HTML元素,
当出现下面这两种情况时,浏览器会显示的内容:
- 浏览器不支持脚本;
- 浏览器对脚本的支持被关闭。
<!DOCTYPE html>
<html>
<head>
<title>Example HTML Page</title>
<script defer="defer" src="example1.js"></script>
<script defer="defer" src="example2.js"></script>
</head>
<body>
<noscript>
<p>This page requires a JavaScript-enabled browser.</p>
</noscript>
</body>
</html>