HTML <script> 元素用于嵌入或引用可执行脚本。

script 元素的属性

script 有以下常见属性:

  • src

这个属性定义引用外部脚本的 URI,这可以用来代替直接在文档中嵌入脚本。指定了 src 属性的 script 元素标签内不应该再有嵌入的脚本。

  • type

该属性定义 script 元素包含或 src 引用的脚本语言类型。属性的值为 MIME 类型; 支持的 MIME 类型包括 text/javascript, text/ecmascript, application/javascript, 和 application/ecmascript 。如果没有定义这个属性,脚本会被视作 JavaScript。

如果 type 属性为 module ,代码会被当作 JavaScript 模块(模块脚本)。

  • async(异步)

对于普通脚本,如果存在 async 属性,那么普通脚本会被并行请求,并尽快解析和执行。
对于模块脚本,如果存在 async 属性,那么脚本及其所有依赖都会在延缓队列中执行,因此它们会被并行请求,并尽快解析和执行。
该属性能够消除解析阻塞的 Javascript。解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本,之后才能继续解析。defer 在这一点上也有类似的作用。

  • defer(推迟)

这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。
有 defer 属性的脚本会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析完成。
如果缺少 src 属性(即内嵌脚本),该属性不应被使用,因为这种情况下它不起作用。
defer 属性对模块脚本没有作用 —— 他们默认 defer。

如何理解 async / defer 和默认的脚本加载执行行为

如下图所示:
script_inner

  1. 当没有 defer 或 async 的时候,浏览器会立即 加载执行 指定的脚本,也就是说不会再解析 script 元素之后的文档元素,读到该脚本就 加载执行执行 之后才会去解析该 script 元素之后的文档元素。
  2. async 脚本将在不阻止解析页面的情况下 下载 脚本,并在脚本完成 下载 后立即 执行 它。也就是说,浏览器会开启新的线程加载 async 脚本,同时在原有线程中解析文档元素。我们无法保证 async 脚本将以任何特定顺序运行,只是 async 的脚本不会阻止页面文档元素的解析。所以 async 最好是当脚本在页面中相互独立运行的并且不依赖页面上的其他脚本时使用。比如 Google Analytics、百度统计 等就适合此种情况。
  3. defer 脚本将在不阻止解析页面的情况下 下载 脚本,但只会在文档元素解析结束后,DOMContentLoaded 事件 之前按照defer 出现的顺序 执行
  4. 同时使用 defer 和 async ,如果浏览器两个属性都支持,则会忽视 defer 属性,按照 async 的方式执行

script元素最佳实践

  1. 将 stylesheet 元素放到 head 中,避免加载 css 文件后引起重绘。
  2. 将 script 元素放到 body 的最后,这样不会因为下载 js 文件阻塞页面的渲染
  3. 对 Google Analytics、百度统计 等与页面元素无关的第三方的 script 元素 添加 async,不阻塞页面的解析
  4. 对于 应用脚本 如果放到了 head 中,可以按照脚本依赖顺序添加 defer,避免因为下载 js 文件阻塞页面的渲染