优化第三方 JavaScript

Milica Mihajlija
Milica Mihajlija

第三方脚本会影响性能,因此请务必定期对其进行审核,并使用高效的加载技术加载这些脚本。此 Codelab 介绍了如何优化第三方资源的加载。它涵盖以下技术:

  • 推迟脚本加载

  • 延迟加载非关键资源

  • 预先连接到必需的源

随附的示例应用包含一个简单的网页,其中包含来自第三方来源的三个功能:

  • 视频嵌入

  • 用于渲染折线图的数据可视化库

  • 社交媒体分享 widget

页面屏幕截图,其中突出显示了第三方资源。
示例应用中的第三方资源。

您将首先衡量应用的性能,然后应用每种方法来改进应用性能的不同方面。

衡量性能

首先,在全屏视图中打开示例应用:

  1. 点击 Remix to Edit 即可修改项目。
  2. 如需预览网站,请按 View App(查看应用)。然后按 Fullscreen(全屏)全屏

对网页运行 Lighthouse 性能审核,以确定基准性能:

  1. 按 `Control+Shift+J`(在 Mac 上为 `Command+Option+J`)打开 DevTools。
  2. 点击 Lighthouse 标签页。
  3. 点击移动
  4. 选中效果复选框。(您可以在“审核”部分清除其余复选框。)
  5. 点击模拟快速 3G,CPU 降速 4 倍
  6. 选中清除存储空间复选框。
  7. 点击运行审核

在计算机上运行审核时,具体结果可能会有所不同,但您应该会注意到 First Contentful Paint (FCP) 时间非常长,并且 Lighthouse 建议您调查以下两个方面:消除阻塞渲染的资源预连接到所需的来源。(即使所有指标均为绿色,优化仍会带来改进。)

Lighthouse 审核的屏幕截图,显示了 2.4 秒的 FCP 和两个优化建议:“消除导致渲染阻塞的资源”和“预先连接到必需的源”。

延迟加载第三方 JavaScript

移除阻塞渲染的资源审核指出,您可以通过推迟来自 d3js.org 的脚本来节省一些时间:

“移除阻塞渲染的资源”审核的屏幕截图,其中突出显示了 d3.v3.min.js 脚本。

D3.js 是一个用于创建数据可视化的 JavaScript 库。示例应用中的 script.js 文件使用 D3 实用程序函数创建 SVG 折线图并将其附加到页面。此处的操作顺序很重要:script.js 必须在解析文档并加载 D3 库后运行,因此它位于 index.html 中的闭合 </body> 标记之前。

不过,D3 脚本包含在页面的 <head> 中,这会阻止解析其余文档:

index.html 的屏幕截图,其中突出显示了 head 中的脚本标记。

将以下两个魔法属性添加到脚本标记中,即可解除对解析器的阻止:

  • async 可确保脚本在后台下载,并在下载完成后在有机会时执行。

  • defer 可确保脚本在后台下载,并在解析完全完成后执行。

由于此图表对整个网页而言并不是至关重要,并且很可能位于折叠线下方,因此请使用 defer 确保没有解析器阻塞。

第 1 步:使用 defer 属性异步加载脚本

index.html 的 17 行中,将 defer 属性添加到 <script> 元素:

<script src="https://d3js.org/d3.v3.min.js" defer></script>

第 2 步:确保操作顺序正确

现在,由于 D3 已延迟,script.js 将在 D3 准备就绪之前运行,从而导致错误。

具有 defer 属性的脚本会按指定的顺序执行。为确保在 D3 准备就绪后执行 script.js,请向其添加 defer,并将其移至文档的 <head> 中,紧跟 D3 <script> 元素后面。现在,它不再阻塞解析器,并且下载会更早开始。

<script src="https://d3js.org/d3.v3.min.js" defer></script>
<script src="./script.js" defer></script>

延迟加载第三方资源

所有位于非首屏的资源都非常适合延迟加载

示例应用在 iframe 中嵌入了 YouTube 视频。如需查看网页发出了多少个请求以及哪些请求来自嵌入的 YouTube iframe,请执行以下操作:

  1. 如需预览网站,请按 View App(查看应用)。然后按 Fullscreen(全屏)全屏
  2. 按 `Control+Shift+J`(在 Mac 上为 `Command+Option+J`)打开 DevTools。
  3. 点击网络标签页。
  4. 选中 Disable cache(停用缓存)复选框。
  5. 节流下拉菜单中,选择快速 3G
  6. 重新加载页面。

DevTools 网络面板的屏幕截图。

Network 面板显示,该网页总共发出了 28 个请求,传输了近 1 MB 的压缩资源。

如需识别 YouTube iframe 发出的请求,请在“发起者”列中查找视频 ID 6lfaiXM6waw。如需按网域对所有请求进行分组,请执行以下操作:

  • 网络面板中,右键点击某个列标题。

  • 在下拉菜单中,选择 Domains(网域)列。

  • 如需按网域对请求进行排序,请点击 Domains(网域)列标题。

重新排序后,您会发现还有其他针对 Google 网域的请求。YouTube iframe 总共发出了 14 次脚本、样式表、图片和字体的请求。但是,除非用户实际滚动到底部播放视频,否则他们实际上并不需要所有这些资源。

通过等到用户滚动到网页的该部分后再延迟加载视频,您可以减少网页最初发出的请求数量。这种方法可以节省用户的数据并加快初始加载速度。

实现延迟加载的一种方法是使用 Intersection Observer,这是一个浏览器 API,可在元素进入或退出浏览器的视口时通知您。

第 1 步:阻止视频在初始加载

如需延迟加载视频 iframe,您必须先阻止以常规方式加载该 iframe。为此,请将 src 属性替换为 data-src 属性以指定视频网址:

<iframe width="560" height="315" data-src="https://www.youtube.com/embed/lS9D6w1GzGY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

data-src 是一个数据属性,可让您在标准 HTML 元素中存储额外信息。数据属性可以使用任何名称,只要以“data-”开头即可。

如果 iframe 不包含 src,则根本无法加载。

第 2 步:使用 Intersection Observer 延迟加载视频

如需在用户滚动到视频时加载视频,您需要知道该操作何时发生。这时,Intersection Observer API 就派上用场了。借助 Intersection Observer API,您可以注册一个回调函数,以便在您要跟踪的元素进入或退出视口时执行该函数。

首先,创建一个新文件,并将其命名为 lazy-load.js

  • 点击新建文件,然后为其命名。
  • 点击添加此文件

将脚本标记添加到文档标头中:

 <script src="/lazy-load.js" defer></script>

lazy-load.js 中,创建一个新的 IntersectionObserver,并向其传递要运行的回调函数:

// create a new Intersection Observer
let observer = new IntersectionObserver(callback);

现在,通过在 observe 方法中将其作为参数传递,为 observer 提供要监控的目标元素(在本例中为视频 iframe):

// the element that you want to watch
const element = document.querySelector('iframe');

// register the element with the observe method
observer.observe(element);

callback 会接收 IntersectionObserverEntry 对象的列表以及 IntersectionObserver 对象本身。每个条目都包含一个 target 元素和用于描述其尺寸、位置、进入视口的时间等的属性。IntersectionObserverEntry 的属性之一是 isIntersecting,这是一个布尔值,当元素进入视口时,其值等于 true

在此示例中,targetiframe。当 target 进入视口时,isIntersecting 等于 true。如需查看此函数的实际应用,请将 callback 替换为以下函数:

let observer = new IntersectionObserver(callback);
let observer = new IntersectionObserver(function(entries, observer) {
    entries.forEach(entry => {
      console.log(entry.target);
      console.log(entry.isIntersecting);
    });
  });
  1. 如需预览网站,请按 View App(查看应用)。然后按 Fullscreen(全屏)全屏
  2. 按 `Control+Shift+J`(在 Mac 上为 `Command+Option+J`)打开 DevTools。
  3. 点击控制台标签页。

请尝试上下滚动。您应该会看到 isIntersecting 的值发生变化,并且目标元素会记录到控制台中。

如需在用户滚动到视频位置时加载视频,请使用 isIntersecting 作为条件来运行 loadElement 函数,该函数会从 iframe 元素的 data-src 获取值,并将其设置为 iframe 元素的 src 属性。此替换会触发视频加载。然后,在视频加载完毕后,对 observer 调用 unobserve 方法以停止监控目标元素:

let observer = new IntersectionObserver(function (entries, observer) {
  entries.forEach(entry => {
    console.log(entry.target);
    console.log(entry.isIntersecting);
  });
});
    if (entry.isIntersecting) {
      // do this when the element enters the viewport
      loadElement(entry.target);
      // stop watching
      observer.unobserve(entry.target);
    }
  });
});

function loadElement(element) {
  const src = element.getAttribute('data-src');
  element.src = src;
}

第 3 步:重新评估效果

如需查看资源的大小和数量有何变化,请打开 DevTools 的 Network 面板,然后重新加载页面。Network 面板显示该网页发出了 14 个请求,而总大小仅为 260 KB。这是一个有意义的改进!

现在,向下滚动页面,并密切关注 Network 面板。进入视频后,您应该会看到该网页触发了其他请求。

预先连接到必需的源

您已推迟非关键 JavaScript 并延迟加载了 YouTube 请求,接下来需要优化其余第三方内容。

向链接添加 rel=preconnect 属性可指示浏览器在发出对该资源的请求之前先建立与网域的连接。此属性最适合用于提供您确定网页需要的资源的来源。

您在第一步中运行的 Lighthouse 审核结果显示,通过提前建立连接到 staticxx.facebook.com 和 youtube.com,您可以节省大约 400 毫秒:预连接到必需的源

突出显示 staticxx.facebook.com 域名的“预先连接到必需的源”审核。

由于 YouTube 视频现在是延迟加载的,因此只剩下 staticxx.facebook.com,它是社交媒体分享 widget 的来源。只需向文档的 <head> 添加 <link> 标记,即可与此网域建立早期连接:

  <link rel="preconnect" href="https://staticxx.facebook.com">

重新评估效果

下面是优化后的网页状态。按照此 Codelab 的衡量性能部分中的步骤,再次运行 Lighthouse 审核。

Lighthouse 审核结果,显示 FCP 为 1 秒,性能得分为 99。