侧边栏壁纸
博主头像
夜语清梦博主等级

读很多的书,走很远的路,见很多的人

  • 累计撰写 5 篇文章
  • 累计创建 3 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

分析 script 标签加载和执行的时机:async、defer 和 防阻塞方法

夜语清梦
2021-07-16 / 0 评论 / 0 点赞 / 176 阅读 / 7432 字

各属性执行时机模拟

尽管诸如 MDN 等文档都描述 asyncdefer 是异步加载,但是为了模拟证明,我用了下面三个文件:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
        }
    </style>
</head>
<body></body>
<script src="https://sakuras.group/sakuras-docs/create.js"></script>
<script src="https://sakuras.group/sakuras-docs/async.js"></script>
</html>
/* create.js */
const newDiv = document.createElement('div');
newDiv.classList.add('box');
document.body.appendChild(newDiv);
// 可以在 create.js 里面复制很长的字符 比如某些库的源码,让 js 通过网络加载慢一些,或者自己用 node 创建个服务开一些接口,指定某个接口延迟返回结果
/* async.js */
const boxElement = document.querySelector(".box");
boxElement.style.backgroundColor = "blue";

将 create.js 和 async.js 放到服务器上,并且将 network 的节流模式换成 低速3G , 然后根据下面的不同场景刷新 html 测试。

没有 async 和 defer

<script src="https://sakuras.group/sakuras-docs/create.js"></script>
<script src="https://sakuras.group/sakuras-docs/async.js"></script>

没有 defer 或 async,浏览器会立即加载并执行指定的脚本,“立即”指的是在渲染该 script 标签之下的文档元素之前,也就是说不等待后续载入的文档元素,读到就加载并执行。因此先创建 DOM,再改变 box 颜色不会报错,反之调换 script 脚本加载顺序则必定报错。

有 async

<script src="https://sakuras.group/sakuras-docs/create.js"></script>
<script async src="https://sakuras.group/sakuras-docs/async.js"></script>

有 async ,async.js 会在 HTML 文档解析时并行下载(异步),并在下载完成后立即执行(暂停 HTML 解析)。由于 async.js 依赖 create.js,如果 async.js 比 create.js 先加载完毕则会立即执行,因此报错,所以是否报错取决于 async.js 会不会加载的比 create.js 快(当然 create.js 代码里执行创建 div 的操作也是异步的)。

在这里不妨设想一下,假如 create.js 和 async.js 都已经缓存过了,当我们刷新页面,显然 create.js 和 async.js 是从缓存里获取资源且耗时 0ms,此时是否应该报错?

  • 我猜测不会报错

  • 经反复刷新测试并没有报错(也不一定不报错只是没试出来,不知道有没有什么好的方法。。。)。

但是如果 create.js 和 async.js 调换一下顺序呢?

<script async src="https://sakuras.group/sakuras-docs/async.js"></script>
<script src="https://sakuras.group/sakuras-docs/create.js"></script>
  • 我猜想是 100% 报错,因为两个 js 都走缓存,当执行 async.js 代码的时候 div 还没有创建,此时一定报错。

  • 但是经过测试,是有很大几率报错,仍然有几率不报错,这意味着存在当 async.js 执行代码的时候,create.js 已经执行过并且 div 已经成功创建,所以这是为什么呢?
    1 可能 async.js 有几率在 create.js 后执行?
    2 可能 async.js 一定在 create.js 后执行,但是 create.js 从执行到创建 div 的这个过程是异步的,不报错是因为 async.js 执行的时候 div 创建完成,报错则是 div 还未创建完成呢?
    3 可能 async.js 从缓存里加载的晚,因此执行的时候 div 经过 create.js 已经创建完毕,所以不报错,报错是因为 async.js 从缓存里加载的快
    我可能更倾向第三种。

有 defer

<script src="https://sakuras.group/sakuras-docs/create.js"></script>
<script defer src="https://sakuras.group/sakuras-docs/async.js"></script>

有 defer,async.js 会在 HTML 文档解析时并行下载 (异步),但是 async.js 的执行会在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成,因此不会报错。

总结

对于 defer,我们可以认为是将外链的 js 放在了页面底部。js 的加载不会阻塞页面的渲染和资源的加载。不过 defer 会按照原本的 js 的顺序执行,所以如果前后有依赖关系的 js 可以放心使用

asyncdefer 一样,会等待的资源不会阻塞其余资源的加载,也不会影响页面的加载。但是有一点需要注意下,在有 async 的情况下,js 一旦下载好了就会执行,所以很有可能不是按照原本的顺序来执行的。如果 js 前后有依赖性,用 async,就很有可能出错。

动态添加的标签隐含 async属性

Defer 和 async 的相同点

  • 加载文件时不阻塞页面渲染

  • 对于 inlinescript 无效

  • 使用这两个属性的脚本中不能调用 document.write 方法

  • 有脚本的 onload 的事件回调

Defer 和 async 的区别

  • html4.0 中定义了 defer;html5.0 中定义了 async

  • 浏览器支持不同

  • 加载时机:

    • 具有 async 属性的脚本都在它下载结束之后立刻执行,同时会在 window 的 load 事件之前执行。所以就有可能出现脚本执行顺序被打乱的情况;

    • 具有 defer 属性的脚本都是在页面解析完毕之后,按照原本的顺序执行,同时会在 document 的 DOMContentLoaded 之前执行。

使用这两个属性会有三种可能的情况

  • 如果 async 为 true,那么脚本在下载完成后异步执行。

  • 如果 async 为 false,defer 为true,那么脚本会在页面解析完毕之后执行。

  • 如果 asyncdefer 都为 false,那么脚本会在页面解析中,停止页面解析,立刻下载并且执行

如何防止 inline script 造成阻塞

问题:

在有 inline script 情况下。下载缓慢的 CSS 可能会阻碍 inline script 的执行。而这又可能阻碍之后的 async script 下载与执行

举个例子:(这里css.php下载缓慢。)

<link rel="stylesheet" href="css1.css.php" type="text/css" /> 
<script src="js1.js" async></script> 
<script>console.log('inline script 1 ' + (+new Date - start));</script> 
<link rel="stylesheet" href="css2.css.php" type="text/css"/> 
<script src="js2.js" async></script> 
<script>console.log('inline script 2 ' + (+new Date - start));</script>

输出结果:

external script 1 87
inline script 1 5184
external script 2 5186
inline script 2 10208
DOMContentLoaded 10216
onload 10227

原因分析

js1 下载执行正常。但是内联 script 和第二个外部 script 因为第一个 CSS 文件(css1.css.php)的缓慢而延迟。DOMContentLoaded 也被阻塞了。 因为内联 script 可能会请求布局信息,为了使其工作,所以要等待 css 下载应用完成。而这又会 block 后续的 async script 的加载。

解决方案:

  1. 把inline script移到页面底部。虽然这仍然阻碍渲染,但是不会阻碍页面资源的下载

  2. 使用setTimeout启动长时间执行的代码

  3. 有一种方法可以使 inline script 以非 inline 的行为处理:将src指向 data:URI。并不需要进行base64编码

把原来的内联 script:

<script>console.log('inline script 1 ' + (+new Date - start));</script>

改写成:

<script async src="data:text/javascript,console.log%28%27inline%20script%201%20%27%20%2B%20%28%2Bnew%20Date%20-%20start%29%29%3B"></script>

这样就可以避免下载缓慢的 css 造成 inline script 对其它 script 文件下载的阻塞。

0

评论区