关于html5:浏览器是如何通过-defer-与-async-属性优化页面加载速度的

95次阅读

共计 4338 个字符,预计需要花费 11 分钟才能阅读完成。

前言

在面试的时候,常常会遇到一道经典的面试题:

如何优化网页加载速度?

惯例的答复中总会有一条:

把 css 文件放在页面顶部,把 js 文件放在页面底部。

那么,为什么要把 js 文件放在页面的最底部呢?

咱们先来看下这段代码:

<!DOCTYPE html>
<html lang="zh">
  <head>
    <title>Hi</title>
    <script>
        console.log("Howdy ~");
    </script>
    <script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/vue-router.global.js"></script>
  </head>
  <body>
    Hello 👋🏻 ~
  </body>
</html>

他的执行程序是:

  • 在控制台打印:Howdy ~
  • 申请并执行 vue.global.js
  • 申请并执行 vue-router.global.js
  • 在页面中展现:Hello 👋🏻 ~
  • 触发 DOMContentLoaded 事件

浏览器的解析规定是:如果遇到 script 标签,则暂停构建 DOM,转而开始执行 script 标签,如果是内部 script,那么浏览器还须要始终期待其「下载」并「执行」后,再持续解析前面的 HTML。

如果申请并执行「vue.global.js」须要 3 秒,「vue-router.global.js」须要 2 秒,那么页面中的 Hello 👋🏻 ~,则至多须要 5 秒以上才会展现进去。

能够看到,script 标签会阻塞浏览器解析 HTML,如果把 script 都放在 head 中,在网络不佳的状况下,就会导致页面长期处于白屏状态。

在很久以前,个别都是将这些外联脚本,放在 body 标签的最初面,确保先解析展现 body 中的内容,而后再一个个申请执行这些外联脚本。

那有没有其余更优雅的解决方案呢?

答案是必定的,当初 script 标签新增了 2 个属性:deferasync,就是为了解决此类问题,晋升页面性能的。

script defer

先看一下 MDN 上的解释:

这个布尔属性被设定用来告诉浏览器该脚本将在文档实现解析后,触发 DOMContentLoaded 事件前执行。

有 defer 属性的脚本会阻止 DOMContentLoaded 事件,直到脚本被加载并且解析实现。

文档是间接总结了他的个性,咱们先看看上面的代码,开展说说细节,加深一下了解。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <title>Hi</title>
    <script>
      console.log("Howdy ~");
    </script>
    <script defer src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
    <script defer src="https://unpkg.com/[email protected]/dist/vue-router.global.js"></script>
  </head>
  <body>
    Hello 👋🏻 ~
  </body>
</html>

他的执行程序是:

  • 在控制台打印:Howdy ~
  • 在页面中展现:Hello 👋🏻 ~
  • 申请并执行 vue.global.js
  • 申请并执行 vue-router.global.js
  • 触发 DOMContentLoaded 事件

如果在 script 标签上设置了 defer 属性,那么在浏览器解析到这里时,会默默的在后盾开始下载此脚本,并持续解析前面的 HTML,并不会阻塞解析操作。

等到 HTML 解析实现之后,浏览器会立刻执行后盾下载的脚本,脚本执行实现之后,才会触发 DOMContentLoaded 事件。

看起来还是蛮好了解的吧?咱们再来探讨 2 个小细节:

Q1: 如果 HTML 解析实现之后,设置了 defer 属性的脚本还没下载实现,会怎么?

A1: 浏览器会等脚本下载实现之后,再执行此脚本,执行实现之后,再触发 DOMContentLoaded 事件。

Q2: 如果有多个设置了 defer 属性的脚本,那浏览器会如何解决?

A2: 浏览器会并行的在后盾下载这些脚本,等 HTML 解析实现,并且所有脚本下载实现之后,再依照他们在 HTML 中呈现的绝对程序执行,等所有脚本执行实现之后,再触发 DOMContentLoaded 事件。

最佳实际:

倡议所有的外联脚本都默认设置此属性,因为他不会阻塞 HTML 解析,能够并行下载 JavaScript 资源,还能够依照他们在 HTML 中的绝对程序执行,确保有依赖关系的脚本运行时,不会短少依赖。

在 SPA 的利用中,能够思考把所有的 script 标签加上 defer 属性,并且放到 body 的最初面。在古代浏览器中,能够并行下载晋升速度,也能够确保在老浏览器中,不阻塞浏览器解析 HTML,起到降级的作用。

留神:

  • defer 属性仅实用于内部脚本,如果 script 脚本没有 src,则会疏忽 defer 个性。
  • defer 属性对模块脚本(script type=’module’)有效,因为模块脚本就是以 defer 的模式加载的。

script async

依照常规,先看一下 MDN 上的解释:

对于一般脚本,如果存在 async 属性,那么一般脚本会被并行申请,并尽快解析和执行。

对于模块脚本,如果存在 async 属性,那么脚本及其所有依赖都会在延缓队列中执行,因而它们会被并行申请,并尽快解析和执行。

该属性可能打消解析阻塞的 Javascript。

解析阻塞的 Javascript 会导致浏览器必须加载并且执行脚本,之后能力持续解析。

感觉这段形容的曾经蛮清晰了,不过咱们还是先看看上面的代码,开展说说细节,加深一下了解。

<!DOCTYPE html>
<html lang="zh">
  <head>
    <title>Hi</title>
    <script>
      console.log("Howdy ~");
    </script>
    <script async src="https://google-analytics.com/analytics.js"></script>
    <script async src="https://ads.google.cn/ad.js"></script>
  </head>
  <body>
    Hello 👋🏻 ~
  </body>
</html>

他的执行程序是:

  • 在控制台打印:Howdy ~
  • 并行申请 analytics.jsad.js
  • 在页面中展现:Hello 👋🏻 ~
  • 依据网络的理论状况,以下几项会无序执行

    • 执行 analytics.js(下载完后,立刻执行)
    • 执行 ad.js(下载完后,立刻执行)
    • 触发 DOMContentLoaded 事件(可能在在下面 2 个脚本之前,之间,之后触发)

浏览器在解析到带有 async 属性的 script 标签时,也不会阻塞页面,同样是在后盾默默下载此脚本。当他下载完后,浏览器会暂停解析 HTML,立马执行此脚本。

看起来还是蛮好了解的吧?咱们再来探讨 2 个小细节:

Q1: 如果设置了 async 属性的 script 下载完之后,浏览器还没解析完 HTML,会怎么?

A1: 浏览器会暂停解析 HTML,立马执行此脚本,等执行完之后,再持续解析 HTML。

Q2: 如果有多个 async 属性的 script 标签,那等他们下载实现之后,会依照代码程序执行吗?

A2: 不会。执行程序是:谁先下载实现,谁先执行。async 的特点是「齐全独立」,不依赖其余内容。

最佳实际:

当咱们的我的项目,须要集成其余独立的第三方库时,能够应用此属性,他们不依赖咱们,咱们也不依赖于他们。
通过设置此属性,让浏览器异步下载并执行他,是个不错的优化计划。

留神:

  • async 个性仅实用于内部脚本,如果 script 脚本没有 src,则会疏忽 async 个性。

总结

defer

  • 不阻塞浏览器解析 HTML,等解析完 HTML 之后,才会执行 script
  • 会并行下载 JavaScript 资源。
  • 会依照 HTML 中的绝对程序执行脚本。
  • 会在脚本下载并执行实现之后,才会触发 DOMContentLoaded 事件。
  • 在脚本执行过程中,肯定能够获取到 HTML 中已有的元素。
  • defer 属性对模块脚本有效。
  • 实用于:所有内部脚本(通过 src 援用的 script)。

async

  • 不阻塞浏览器解析 HTML,然而 script 下载实现后,会立刻中断浏览器解析 HTML,并执行此 script
  • 会并行下载 JavaScript 资源。
  • 相互独立,谁先下载完,谁先执行,没有固定的先后顺序,不可控。
  • 因为没有确定的执行机会,所以在脚本外面可能会获取不到 HTML 中已有的元素。
  • DOMContentLoaded 事件和 script 脚本无相关性,无奈确定他们的先后顺序。
  • 实用于:独立的第三方脚本。

另外:asyncdefer 之间最大的区别在于它们的执行机会。

One More Thing

你有没有想过,如果一个 script 标签同时设置 deferasync,浏览器会如何解决?

先说论断:从表现形式上来说,async 的优先级比 defer 高,也就是如果同时存在这 2 个属性,那么浏览器将会以 async 的个性去加载此脚本。

这次要分 2 种状况:
如果是「一般脚本」,浏览器会优先判断 async 属性是否存在,如果存在,则以 async 个性去加载此脚本,如果不存在,再去判断是否存在 defer 属性。

如果是「模块脚本」,浏览器会判断 async 属性是否存在:

  • 如果存在,浏览器会并行下载此模块和他的所有依赖模块,等全副下载实现之后,会立即执行此脚本。
  • 如果不存在,浏览器也会并行下载此模块和他的所有依赖模块,而后等浏览器解析完 HTML 之后,再执行此脚本。
  • 另外须要留神的是:在模块脚本上设置 defer 属性是有效的。

一图胜千言

最初,用一张图概括一下这两个属性的加载模式吧:

思考题 🤔

  • 为什么浏览器在解析到一般的 script 标签时,必须先执行他?
  • 一般的 script 标签会阻塞浏览器解析 HTML,这会导致什么问题?

本文首发于:https://github.com/mrlmx/blog…,如果喜爱,记得去点个赞哦~ 👍 ❤️

参考

  • https://javascript.info/script-async-defer
  • https://www.growingwiththeweb.com/2014/02/async-vs-defer-attributes.html
  • https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/script
  • https://html.spec.whatwg.org/multipage/scripting.html

正文完
 0