首屏优化002-异步加载脚本

203 阅读3分钟

延迟加载脚本意味着将脚本的加载推迟到页面的其他内容加载完成之后再进行。这可以通过将 <script> 标签放置在页面底部实现,或者使用 deferasync 属性来控制脚本的加载行为。

一、基本概念

1、HTML文档的解析

是指浏览器将接收到的 HTML 代码转换为 DOM树的过程。当浏览器接收到 HTML 代码时,它会逐行解析代码,构建出 DOM 树的结构,包括 HTML 元素、文本内容、属性等。

2、页面的加载

是指浏览器从服务器获取页面所需的各种资源,并将这些资源加载到浏览器中显示出来的过程。除了 HTML 文件本身,页面通常还包含其他资源,如 CSS 文件、JavaScript 文件、图片、字体等。在页面加载过程中,浏览器会根据 HTML 解析过程中遇到的资源链接,发起请求并下载这些资源。下载完成后,这些资源将被浏览器解析和渲染,最终呈现在用户的浏览器窗口中。

可以将 HTML 解析看作是页面加载的一个阶段,它是页面加载的起点。HTML 解析完成后,浏览器开始根据解析得到的 DOM 树进行后续的资源加载和渲染。页面加载过程中还包括了网络请求、资源下载、CSS 解析、JS解析与执行等环节。

3、DOMContentLoaded = HTML文档已经完全加载和解析

当浏览器解析完页面的 HTML 结构并构建了 DOM 树之后,会触发DOMContentLoaded 事件。这个事件表示页面的初始 HTML 文档已经完全加载和解析,并且 DOM 树已经准备就绪,此时可以对 DOM 进行操作。DOMContentLoaded 事件的触发时机早于其他资源(如样式表、图像、脚本等)的加载和渲染完成,因此可以在该事件中执行一些初始化操作,如注册事件处理程序、操作 DOM 元素等。

需要注意的是

  • DOMContentLoaded 事件不考虑外部资源的加载状态,只关注页面的初始文档结构。如果页面中包含外部资源(如样式表、图像、脚本等),这些资源的加载和渲染可能会在 DOMContentLoaded 事件执行之后触发。如果需要在页面完全加载完成后再执行某些操作,可以使用 window.onload 事件,该事件在页面的所有资源(包括外部资源)加载完成后触发。

4、script标签有哪些属性

<script> 标签在 HTML 中用于加载和执行 JavaScript 代码。以下是五个常用的 <script> 标签的重要属性及其解释:

  1. src 属性用于指定要加载的外部JS文件的 URL(相对路径或者绝对URL)。
  2. async 属性用于异步加载脚本文件。
  3. defer 属性用于延迟执行脚本文件。
  4. type 属性用于指定脚本文件的 MIME 类型。属性默认值为 text/javascript,可以省略。示例:<script src="script.js" type="text/javascript"></script>
  5. charset 属性用于指定脚本文件的字符编码。它可以帮助确保脚本文件以正确的编码方式解析和执行,避免出现乱码或其他字符编码相关问题。常用的字符编码包括 "UTF-8"(默认)、"ISO-8859-1" 等。示例:<script src="script.js" charset="UTF-8"></script> 这些属性可以单独或组合使用,根据实际需求来控制脚本的加载和执行方式,以优化页面性能和脚本的可维护性。
  6. 后续补充

二、延迟加载脚本

JS脚本的下载 与 页面其他资源的下载 是 异步并发执行的

  • 当浏览器解析 HTML 文档时,遇到 <script> 标签时会立即开始下载js脚本,并继续解析 HTML 的其他部分。在下载 JavaScript 脚本的同时,浏览器会继续下载页面中的其他资源,如样式表、图片等。

1. 将 <script> 标签放置在</body>

这样浏览器在解析页面时就可以优先加载和渲染页面内容,而不会被脚本的加载阻塞。

<body>
  <!-- 页面内容 -->
  <script src="path/to/script.js"></script>
</body>

2. 使用 defer 属性

前端脚本的 defer 属性的全称是 "deferred",意为 "推迟"。它是 <script> 标签的一个属性,用于控制脚本的加载和执行行为。

<script src="path/to/script.js" defer></script>
  • defer 属性告诉浏览器推迟脚本的执行
  • 脚本的下载不会阻塞 HTML 文档的解析和渲染过程。(遇到带有 defer 属性的 <script> 标签,它会异步下载该脚本文件)
  • 第一个defer 脚本的执行时机:DOMContentLoaded 事件之前
  • 多个defer脚本执行的顺序,是按照它们在 HTML 中的上下书写顺序进行加载

注意

  • defer 属性只对外部脚本文件(通过 src 属性引入的脚本)有效,对于内联脚本(直接写在 <script> 标签内部的脚本)无效。
  • 疑问 如果脚本中存在对 DOM 的操作,仍需等待整个文档解析完毕后执行。因此,在使用 defer 属性时,需要根据实际情况合理编写脚本,以确保正确的执行顺序和逻辑。

使用 defer 属性的好处是,脚本的加载和执行与页面内容的解析和渲染可以并行进行,不会阻塞页面的呈现。这有助于提高网页的加载性能和渲染速度,尤其对于位于 <head> 标签中的脚本文件更为有效,因为这样可以避免阻塞首屏的呈现。

3. 使用 async 属性

<script src="path/to/script.js" async></script>
  • async 属性告诉浏览器异步加载脚本,不会阻塞页面的加载和渲染。脚本的加载和执行是异步进行的,脚本下载完成后立即执行,不保证按照在 HTML 中的顺序执行。
  • 如果多个脚本都使用了 async 属性,它们之间的加载顺序是不确定的,取决于下载完成的先后顺序(所以可以与HTML文档中的顺序无关)。
  • 加载完成=下载完成。脚本的加载完成后会立即执行,不会等待其他资源的加载完成。

4. 其他

  • 使用 deferasync 属性可以提高网页的加载性能,因为它们使得脚本的加载和执行与页面内容的解析和渲染可以并行进行,而不会阻塞页面的呈现。
  • 注意,使用 deferasync 属性时,应确保脚本之间的依赖关系正确处理。如果脚本之间存在依赖关系,需要保证它们的加载顺序或使用模块化加载器来管理模块的加载和执行。

总结

  • 遇到<script async>脚本或者<script defer>,都会马上去异步下载(或者名为加载)对应的JS资源。遇到普通<script>脚本是同步下载,下载和执行过程会阻塞HTML解析
  • defer是在DOMContentLoaded 事件之前执行。而且多个defer之间是按照书写顺序执行的。
  • async是下载完就马上执行。多个async的执行顺序与下载完成的顺序一致,与书写顺序无关。async 脚本的执行可能发生在 DOMContentLoaded 事件触发之前或之后,具体取决于脚本下载和执行的时间. 每个脚本的内部可能都有延时的逻辑,这个"执行完成位置"不可控。

image.png 截图的github地址:待定github

三、按需加载脚本(高级)

将页面上不需要立即加载的脚本延迟加载,只在需要时再动态加载。

1. 动态创建 <script> 标签

在需要加载脚本的时候,使用 JavaScript 动态创建 <script> 标签,并设置其 src 属性来加载脚本。

function loadScript(url) {
  const script = document.createElement('script');
  script.src = url;
  document.body.appendChild(script);
}

// 调用示例
loadScript('path/to/script.js');

2. 使用模块化加载器

使用模块化加载器(如 RequireJS、SystemJS、Webpack 等)可以按需加载模块和脚本,避免一次性加载所有脚本。

// 使用 RequireJS 加载模块
require(['path/to/module'], function (module) {
  // 执行模块逻辑
});

3. 分片加载(Code Splitting)

将代码分割成多个小块(chunk),根据需要按需加载。

使用 Webpack 进行代码分割

Webpack 可以将代码分割成多个小块,根据路由或按需加载的需求进行动态加载。

import(/* webpackChunkName: "my-chunk" */ 'path/to/module')
  .then((module) => {
  // 执行模块逻辑
});