06、js 延迟加载-defer、async 属性


教程简介

  • 1、阅读对象
    只要对 js 基础掌握不牢的都适合
  • 2、教程难度
    初级「但是你不一定知道」,本人水平有限,文章内容难免会出现问题,如果有问题欢迎指出,谢谢

正文

一、先有 DOM 才能操「扯」作「蛋」

JavaScript 要操作 DOM 前提是 DOM 要生成,上节说过 docuemnt 的 DOMContentLoaded、window.onload 页面生命周期方法都可以让 JavaScript 来进行操作 DOM,当然我们也可以尽可能的让 DOM 解析完然后再执行 JavaScript 代码,这就到了这节的重点–defer 和 async 属性

二、为什么要有 defer、async

JavaScript 外链式必须要有 src 属性

<script src="outer.js"></script>

如果没有 defer/async 属性,浏览器就会立即下载 outer.js 脚本,并且浏览器暂停解析,直到脚本下载完成并执行完成,浏览器继续解析,在这个过程中如果 outer.js 脚本非常大那下载过程会很慢,此时浏览器解析暂停着,给用户的感觉就是网页界面卡住了「体验很不好」

加载中

此时 async/defer 就有用武之地了

三、defer、async 的执行情况

先看看浏览器遇到 script 标签三种情况「外链式」

1、普通 script

<script src="outer.js"></script>

普通 js

如图所示:普通的 script 加载外部 js 浏览器执行情况

  • 1、浏览器进行页面解析「html 解析–生成 DOM 树」
  • 2、当遇到 script 标签立即暂停解析,去下载 outer.js 脚本
  • 3、脚本下载完毕,执行 outer.js
  • 4、outer.js 执行完毕,浏览器继续解析 html

整个过程是一个串行的过程,前一个干完再干后一件事情,页面在暂停的时刻就是耗时之处

2、有 async 属性

<script src="outer.js" async></script>

从普通 script 标签我们了解到了页面解析和下载 js 是互拆的,那能不下载 js 的时候页面不暂停解析,当然可以,这就是 async 的作用「异步指的就是页面解析和下载 js 是异步的」,如图所示

async 延迟加载

浏览器执行情况如下:

  • 1、浏览器进行页面解析「html 解析–生成 DOM 树」
  • 2、当遇到 script 标签一看有 async 属性则下载 outer.js 脚本,此时解析 html 继续执行不中断
  • 3、直到 outer.js 脚本下载完成,执行 outer.js 的时候浏览器暂停解析 html
  • 4、直到 outer.js 执行完毕,浏览器继续解析 html

3、有 defer 属性

<script src="outer.js" defer></script>

从字面可以看出来 defer 是延迟加载的意思,其与 async 又不同,defer 是延迟 js 的执行「在界面解析完毕之后再执行 js 」并且界面解析和下载 js 同时进行,如下图所示:

defer 延迟加载

浏览器的执行情况如下:

  • 1、浏览器进行页面解析「html 解析–生成 DOM 树」
  • 2、当遇到 script 标签一看有 defer 属性则下载 outer.js 脚本,此时解析 html 继续执行不中断
  • 3、直到 outer.js 脚本下载完成此时不会立即执行 outer.js 而是继续解析 html
  • 4、等 html 解析完毕,再执行 outer.js

四、Demo 验证

1、async 和普通 js 加载情况

(1)、创建以下几个文件

分别新建 index.html 文件,normal.js outer.js outer1.js 文件

文件目录

(2)、各文件内容

  • index.html 内容
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>js 延迟加载</title>
    <!-- 使用 async 属性 -->
    <script src="outer.js" async></script>
    <!-- 使用 async 属性  -->
    <script src="outer1.js" async></script>
    <script src="normal.js"></script>
</head>
<body>
    <div>
        <ol>
            <li>emel1</li>
            <li>emel2</li>
            <li>emel3</li>
            <li>emel4</li>
        </ol>
    </div>
</body>
</html>
  • normal.js 中直接输出 console.log(‘nromal’);
  • outer.js 引入了 vue.js 的源码大概 10948 行代码「内容非常大,下载则耗时」
  • outer1.js 直接输出 console.log(‘outer1.js’);

(3)、查看结果

查看结果

多次运行得出结论 outer.js 内容输出总在 outer1.js 之后,为什么这样呢?由于 outer.js 的内容大下载耗时并且此处属性是 async 所以下载完后直接执行其中的 js 内容所以它相对来说后执行,async 就是谁先下载完谁先执行「乱序执行」

2、defer 和普通 js 加载情况

将上述 async 属性改为 defer 属性

<!-- 使用 defer 属性 -->
<script src="outer.js" defer></script>
<!-- 使用 defer 属性  -->
<script src="outer1.js" defer></script>
<script src="normal.js"></script>

我们来看看效果

查看结果

js 外链有 defer 属性的会按顺序执行 js 的内容「下载 js 是异步的相对于浏览器解析」并且其执行机制基本上是在所有元素解析完成「顺序执行的」 DOMContentLoaded 事件触发之前执行的

3、async 遇到 DOMContentLoaded

1、修改 index.html

<script src="outer.js" defer></script>
<script src="outer1.js" async></script>
<script src="normal.js"></script>

将 outer.js 改为 defer

2、分别在 outer.js「在 vue 源码之后添加」 和 outer1.js 添加如下代码

// outer.js
document.addEventListener('DOMContentLoaded',function () {
    let lis = document.getElementsByTagName('li') ;
    console.log('outer.js-->li 的数量 '+lis.length) ;
})

下面是 outer1.js

// outer1.js
document.addEventListener('DOMContentLoaded',function () {
    let lis = document.getElementsByTagName('li') ;
    console.log('outer1.js-->li 的数量 '+lis.length) ;
})

查看结果

查看结果

我们可以看到使用 async 和 defer 属性 DOMContentLoaded 都执行了,但是事实是如此吗?不是的,我们再修改 outer.js 为 async 查看结果

查看结果

我们多刷新几次浏览器看到 outer.js 中有 DOMContentLoaded 的事件会出现没有调用的情况,为什么呢?那就是如果使用 async 属性时 DOMContentLoaded 事件有时会在 js 代码执行前调用,有时会在 js 执行后调用「此时会响应 DOMContentLoaded 事件」

很好理解,由于 async 属性是下载完 js 直接执行 js 中的代码,那么有两种情况「DOMContentLoaded 简称 DCL」:

  • 1、浏览器还没有解析完 html,async 的 js 脚本下载完立即执行「此时 html 停止解析」,脚本执行完以后,浏览器继续解析完基本的 DOM 然后 DOMContentLoaded 事件响应「输出 DOMContentLoaded 中的代码」

DCL 在 js 脚本执行后

  • 2、浏览器解析完毕调用 DOMContentLoaded 事件「提前调用了」,此时 js 还没有下载完,「没有输出 DOMContentLoaded 中的代码」,js 都没有运行,想执行 DOMContentLoaded 事件肯定是执行不了的

DCL 在 js 脚本执行前

所以建议使用 async 属性时不要使用 DOMContentLoaded 事件

4、defer 遇到 DOMContentLoaded

1、修改 index.htlm 中 outer.js 和 outer1.js 的属性为 defer

<script src="outer.js" defer></script>
<script src="outer1.js" defer></script>
<script src="normal.js"></script>

2、查看结果

defer 遇到 DCL

则可以看到 DOMContentLoaded 中的代码执行了,由此可知 DOMContentLoaded 是在 html 解析完成 defer 脚本执行完后才执行的,其也分为两种情况

  • 1、defer 下载完成,浏览器才解析完 html「生成基本 DOM」,然后执行 defer 脚本,再触发 DOMContentLoaded

html 在 defer 加载完成之前解析完毕

  • 2、浏览器解析完 html「生成基本 DOM」,defer 未载完直到下载完,并且执行完,再触发 DOMContentLoaded

html 在 defer 未加载完成之前解析完毕

五、总结

  • 1、async 属性:下载 js 脚本不阻塞 html 解析,并且下载完 js 立即执行 js 脚本「并且多个 async 属性的 script 是乱序执行的」
  • 2、defer 属性:下载 js 脚本不阻塞 html 解析,但执行 js 脚本是在 html 解析完毕之后「基本 DOM 生成」,并且多个 defer 属性的 script 是顺序执行的
  • 3、js 脚本的执行和 html 解析永远都是互斥的
  • 4、async 遇到 DOMContentLoaded,DOMContentLoaded 事件有可能在 async 脚本执行之前触发,也有可能在 async 脚本执行之后触发
  • 5、defer 遇到 DOMContentLoaded,DOMContentLoaded 事件是在 defer 脚本执行完毕后触发

本节到此为止,我们下节见


交个朋友

如果觉得本篇对你有帮助,那么请你完成以下几件小事情

1、动动你的小手关注一下以下公众号「TigerChain」查看更多精彩分享

2、更多视频关注的我的 B站:https://space.bilibili.com/44242327/


文章作者: TigerChain
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 TigerChain !
评论
  目录