HTML 中的 JavaScript
将 JavaScript 引入网页,首先要解决它与网页的主导语言 HTML 的关系问题。在 JavaScript 早期, 网景公司的工作人员希望在将 JavaScript 引入 HTML 页面的同时,不会导致页面在其他浏览器中渲染出 问题。通过反复试错和讨论,他们最终做出了一些决定,并达成了向网页中引入通用脚本能力的共识。当初他们的很多工作得到了保留,并且最终形成了 HTML 规范。
<script> 元素
属性
<script>
元素在 HTML 中有多种属性,用于控制 JavaScript 脚本的加载和执行方式。以下是 <script>
元素的所有主要属性及其作用:
-
src
: 指定外部 JavaScript 文件的URL。 -
type
: 表示代码块中脚本语言的内容类型(也称 MIME 类型), 该属性的值可能为以下类型:
- 属性未设置(默认),一个空字符串,或一个 JavaScript MIME 类型。
- 如果这个值是
module
,则代码会被当成 ES6 模块,而且只有这时候代码中才能出现 import 和 export 关键字。 - importmap: 此值代表元素体内包含导入映射(
importmap
)表。导入映射表是一个 JSON 对象,开发者可以用它来控制浏览器在导入 JavaScript 模块时如何解析模块标识符。
-
async
: 表示应该立即开始下载脚本,但不能阻止其他页面动作,比如下载资源或等待其 他脚本加载。只对外部脚本文件有效。 -
defer
: 表示脚本可以延迟到文档完全被解析和显示之后再执行。只对外部脚本文件有效。 -
integrity
: 提供加密签名,确保外部脚本在传输过程中没有被篡改。 -
crossorigin
: 配置跨域请求的方式。常用值有anonymous
和use-credentials
。 -
nomodule
: 这个布尔属性被设置来标明这个脚本不应该在支持 ES 模块的浏览器中 执行。实际上,这可用于在不支持模块化 JavaScript 的旧浏览器中提供回退脚本。 -
nonce
-
referrerpolicy
在不使用 defer
和 async
属性的情况下,包含在 <script>
元素中的代码必须严格按次序解释。
另外,使用了 src 属性的 <script>
元素不应该再在 <script>
和 </script>
标签中再包含其他 JavaScript 代码。如果两者都提供的话,则浏览器只会下载并执行脚本文件,从而忽略行内代码。
<script>
元素的一个最为强大、同时也备受争议的特性是,它可以包含来自外部域的 JavaScript 文件。跟 <img>
元素很像,<script>
元素的 src 属性可以是一个完整的 URL,而且这个 URL 指向的 4 资源可以跟包含它的 HTML 页面不在同一个域中,比如这个例子:
<script src="http://www.somewhere.com/afile.js"></script>
按照惯例,外部 JavaScript 文件的扩展名是.js。这不是必需的,因为浏览器不会检 查所包含 JavaScript 文件的扩展名。这就为使用服务器端脚本语言动态生成 JavaScript 代 码,或者在浏览器中将 JavaScript 扩展语言(如 TypeScript,或 React 的 JSX)转译为 JavaScript 提供了可能性。不过要注意,服务器经常会根据文件扩展来确定响应的正确 MIME 类型。 如果不打算使用.js 扩展名,一定要确保服务器能返回正确的 MIME 类型。
标签位置
过去,所有 <script>
元素都被放在页面的 <head>
标签内,如下面的例子所示:
<!doctype html>
<html lang="en">
<head>
<title>Example HTML Page</title>
<script src="example1.js"></script>
<script src="example2.js"></script>
</head>
<body>
<!-- 这里是页面内容 -->
</body>
</html>
这种做法的主要目的是把外部的 CSS 和 JavaScript 文件都集中放到一起。不过,把所有 JavaScript 文件都放在<head>
里,也就意味着必须把所有 JavaScript 代码都下载、解析和解释完成后,才能开始渲 染页面(页面在浏览器解析到<body>
的起始标签时开始渲染)。对于需要很多 JavaScript 的页面,这会 导致页面渲染的明显延迟,在此期间浏览器窗口完全空白。为解决这个问题,现代 Web 应用程序通常 将所有 JavaScript 引用放在<body>
元素中的页面内容后面,如下面的例子所示:
<!doctype html>
<html lang="en">
<head>
<title>Example HTML Page</title>
</head>
<body>
<!-- 这里是页面内容 -->
<script src="example1.js"></script>
<script src="example2.js"></script>
</body>
</html>
推迟执行脚本
HTML 4.01 为 <script>
元素定义了一个叫 defer
的属性。这个属性表示脚本在执行的时候不会改变页面的结构。也就是说,脚本会被延迟到整个页面都解析完毕后再运行。因此,在 <script>
元素上 设置 defer
属性,相当于告诉浏览器立即下载,但延迟执行。
<!doctype html>
<html lang="en">
<head>
<title>Example HTML Page</title>
<script defer src="example1.js"></script>
<script defer src="example2.js"></script>
</head>
<body>
<!-- 这里是页面内容 -->
</body>
</html>
虽然这个例子中的 <script>
元素包含在页面的 <head>
中,但它们会在浏览器解析到结束的 </html>
标签后才会执行。HTML5 规范要求脚本应该按照它们出现的顺序执行,因此第一个推迟的脚 本会在第二个推迟的脚本之前执行,而且两者都会在 DOMContentLoaded 事件之前执行。不过在实际当中,推迟执行的脚本不一定总会按顺序执行或者在 DOMContentLoaded 事件之前执行,因此最好只包含一个这样的脚本。
异步执行脚本
HTML5 为 <script>
元素定义了 async
属性。从改变脚本处理方式上看,async
属性与 defer
类似。当然,它们两者也都只适用于外部脚本,都会告诉浏览器立即开始下载。不过,与 defer
不同的 是,标记为 async
的脚本并不保证能按照它们出现的次序执行,比如:
<!doctype html>
<html lang="en">
<head>
<title>Example HTML Page</title>
<script async src="example1.js"></script>
<script async src="example2.js"></script>
</head>
<body>
<!-- 这里是页面内容 -->
</body>
</html>
在这个例子中,第二个脚本可能先于第一个脚本执行。因此,重点在于它们之间没有依赖关系。给脚本添加 async
属性的目的是告诉浏览器,不必等脚本下载和执行完后再加载页面,同样也不必等到 该异步脚本下载和执行后再加载其他脚本。正因为如此,异步脚本不应该在加载期间修改 DOM。
异步脚本保证会在页面的 load 事件前执行,但可能会在 DOMContentLoaded之前或之后。
async
和 defer
是现代网页开发中常用的优化手段,可以帮助开发者更好地管理脚本加载和执行顺序。在实际项目中,根据脚本的依赖关系和执行需求,合理使用这两个属性,可以显著提升网页性能和用户体验。理解和应用这些优化技巧,是前端开发的重要技能之一。