异步加载 script 脚本

方式一:async 和 defer

  1. <script src="xxx" defer></script>
  2. <script src="xxx" async></script>

image.png
deferasync 机制都是异步(相较于 HTML 解析)加载 js 脚本,但 js 脚本的执行时间有所区别

  1. defer 要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行(即渲染完再执行),而且是按照defer 脚本的声明顺序来执行脚本的
  2. async 一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染(下载完就执行),多个async 脚本是不能保证加载顺序的

    方式二:加载 ES6 模块

    1. <script src="xxx" type="module"></script>
    <script>标签加入type="module"属性,可以让浏览器异步加载 ES6 模块,不会造成阻塞,即等到整个页面渲染完,再执行模块脚本,等同于<script>标签加入defer属性

对于外部的 ES6 模块脚本,有几点需要注意:

  • 代码是在模块作用域中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
  • 模块脚本自动采用严格模式,不管有没有声明 use strict。
  • 模块中,可以使用 import 加载其他模块(.js 后缀不可省略),也可以使用 export 输出对外接口。
  • 模块中,顶层的 this 关键字返回 undefined,而不是指向 window。
  • 同一个模块如果加载多次,将只执行一次。

方式三:动态创建 script 标签

动态创建的 script ,设置 src 并不会开始下载,而是要添加到文档中,JS 文件才会开始下载

方式四:XHR 异步获取 JS

  1. let xhr = new XMLHttpRequest();
  2. xhr.open("get", "js/xxx.js",true);
  3. xhr.send();
  4. xhr.onreadystatechange = function() {
  5. if (xhr.readyState == 4 && xhr.status == 200) {
  6. eval(xhr.responseText);
  7. }
  8. }

预加载文件

preload 和 prefetch

preload

使用 preload 可以对当前页面所需的脚本、样式等资源进行预加载,而无需等到解析到 script 和 link 标签时才进行加载。这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的初步渲染,进而提升性能。

使用方式

将 link 标签的 rel 属性的值设为 preload,as 属性的值为资源类型(如脚本为 script,样式表为 style)

  1. <head>
  2. <!-- 对 style.css 和 index.js 进行预加载 -->
  3. <link rel="preload" href="style.css" as="style"/>
  4. <link rel="preload" href="index.js" as="script"/>
  5. </head>

prefetch

prefetchpreload 一样,都是对资源进行预加载,但是 prefetch 一般预加载的是其他页面会用到的资源。

prefetch 不会像 preload 一样,在页面渲染的时候加载资源,而是利用浏览器空闲时间来下载。

当进入下一页面,就可直接从 disk cache 里面取,既不影响当前页面的渲染,又提高了其他页面加载渲染的速度。

使用方式

将 link 标签的 rel 属性的值设为 prefetch,as 属性的值为资源类型(如脚本为 script,样式表为 style)

  1. <head>
  2. <!-- 对资源进行 prefetch 预加载 -->
  3. <link rel="prefetch" href="next.css" />
  4. <link rel="prefetch" href="next.js" />
  5. </head>

总结:对当前页面需要的资源,使用 preload 进行预加载。对其它页面需要的资源进行 prefetch 预加载

subresource 和 prerender

subresource

  1. <link rel="subresource" href="next.js" />

subresource 可以用来指定资源是当前页面资源的最高优先级。如果资源马上就会用到,推荐使用 subresource

prerender

  1. <link rel="prerender" href="/thenextpage.html" />

prerender 是一个重量级的选项,它可以让浏览器提前加载指定页面的所有资源。

prerender 就像是在后台打开了一个隐藏的 tab,会下载所有的资源、创建DOM、渲染页面、执行js 等等。如果用户进入指定的链接,隐藏的这个页面就会立马进入用户的视线。

可以利用 Page Visibility API 来防止页面在还没真正展示给用户时就触发了 js 的执行。

要注意,一定要在十分确定用户会点击某个链接时才使用该特性,否则客户端会无端的下载很多资源和渲染这个页面。

正如任何提前动作一样,预判总是有一定风险出错。如果提前的动作是昂贵的(比如高CPU、耗电、占用带宽),就要谨慎使用了。

提前建立链接

dns-prefetch

DNS prefetching 通过指定具体的 URL 来告知客户端未来会用到相关的资源,这样浏览器可以尽早的解析 DNS

  1. <link rel="dns-prefetch" href="xxx">

preconnect

让浏览器在一个 HTTP 请求正式发给服务器前预先执行一些操作,建立与服务器的连接,这包括 DNS 解析,TLS 协商,TCP 握手,这消除了往返延迟并为用户节省了时间

  1. <link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>

关于异步加载资源 - 图2

与 dns-prefetch 配合

  1. <link rel="preconnect" href="https://fonts.gstatic.com/" crossorigin>
  2. <link rel="dns-prefetch" href="https://fonts.gstatic.com/">

如果页面需要建立与许多第三方域的连接,则将它们预先连接会适得其反。 preconnect 提示最好仅用于最关键的连接。对于其他的,只需使用 即可节省第一步的时间 DNS 查找

参考资料

《js异步加载(defer、async、module)和预加载(preload、prefetch、subresource、prerender)前端文件》
《Page Visibility API 教程》
《link标签的 preload、prefetch、preconnect》
《Link 标签属性含义与详细用法》