一文解决async defer prefetch preload @import link:media dns-prefetch (上)

如果将async defer prefetch preload @import link:media dns-prefetch称之为对html文件的配置,那么这些配置对于初学者来说很难理解的,本文分为上下两个部分对这些概念进行分析,力求做到简单易懂。

1. 测试用的项目构建

bash 复制代码
yarn init - y
yarn add express
touch fast.js
touch slow.js
touch null.js
touch index.html

往index.html中填入内容:

html 复制代码
<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Document</title>

    <script>

        var _now = +new Date();

    </script>

    <script src="http://localhost:3000/null"></script>

    <!-- <script src="http://localhost:3000/slow" async></script> -->

    <!-- <script src="http://localhost:3000/fast" async></script> -->

</head>

<body>

    <h1>我出现了!</h1>

</body>

</html>

2. 文件内容

indx.html中使用到的三个脚本文件的内容分别为:

js 复制代码
console.log('白屏1秒');
console.log('fast')
console.log('slow')

注意index.html中后面两个js的引入目前注释掉了!

3. 后端服务器构造

显然需要一个都唔起,这里使用express提供服务:touch server.js

js 复制代码
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();

app.get('/fast',(req,res)=>{
    setTimeout( () => {
        const fileContent = fs.readFileSync(path.join(__dirname, './fast.js'));
        res.end(fileContent);  
    } , 2000)
})

app.get('/slow',(req,res)=>{
    setTimeout( () => {
        const fileContent = fs.readFileSync(path.join(__dirname, './slow.js'));
        res.end(fileContent);  
    } , 3000)
})

app.get('/null',(req,res)=>{
    setTimeout( () => {
        const fileContent = fs.readFileSync(path.join(__dirname, './null.js'));
        res.end(fileContent);  
    } , 1000)
})

app.listen(3000, ()=>{
    console.log('done!')
})

4. 测试

4.1 测试1 -- 检测async的功能

将index.html中的

<script src="http://localhost:3000/null"></script>

改成

<script src="http://localhost:3000/null" **async**></script>

然后在浏览器中分别打开修改前后的index.html:

  • 修改之前:
  • 修改之后:
  • 对比一下:
    • 改变的点有:null.js的优先级、有没有阻塞DOM渲染(即有没有白屏1s)、DOMContentLoaded的值从1.02s变成了33ms
    • 不变的点有:连接ID没有复用、加载总用时没有改变、index.html的优先级始终是最高!

4.2 测试2 -- 检测defer的功能

将index.html中的

<script src="http://localhost:3000/null"></script>

改成

<script src="http://localhost:3000/null" defer></script>

然后在浏览器中分别打开修改前后的index.html。

直接说结论 :在这种情况下和async的表现完全一致

4.3 测试3 -- 检测async和defer的区别

4.3.1 将index.html中改成

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        var _now = +new Date();
    </script>
    <!-- <script src="http://localhost:3000/null" defer></script> -->
    <script src="http://localhost:3000/slow" async></script>
    <script src="http://localhost:3000/fast" async></script>
</head>
<body>
    <h1>我出现了!</h1>
</body>
</html>

注意此时slow在fast前面:

打开index.html发现:控制台先打印出了fast后打印出slow:

现在改变两个资源的位置:

html 复制代码
<script src="http://localhost:3000/fast" **async**></script>
<script src="http://localhost:3000/slow" **async**></script>

这说明了async的特点在于:异步、无序、下载完成就执行,其不阻塞DOM仅在于其下载阶段,一旦下载完成就开始执行,执行过程中当然会阻塞。

4.3.2 将index.html中改成

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        var _now = +new Date();
    </script>
    <!-- <script src="http://localhost:3000/null" defer></script> -->
    <script src="http://localhost:3000/fast" defer></script>
    <script src="http://localhost:3000/slow" defer></script>  
</head>
<body>
    <h1>我出现了!</h1>
</body>
</html>

注意此时fast在slow前面:

打开index.html发现:控制台先打印出了fast后打印出slow:

这是合情合理的,但是一旦调换这两个资源的位置:

html 复制代码
<script src="http://localhost:3000/slow" defer></script>
<script src="http://localhost:3000/fast" defer></script>

再打开html就会发现:

从瀑布图上可以清楚的看到,就算是fast资源首先被请求回来了,但是还是一直等slow资源,等slow资源请求回来并且执行之后,fast才执行。

总之defer和async都不会在一开始阻塞DOM的渲染;async的特点是异步、无序、加载完执行;而defer的特点是异步、有序、DOM加载完再执行。所以说async不阻塞DOM其实是不准确的。

4.4 测试4 -- async混用

将index.html改成如下所示的样子:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script>
        var _now = +new Date();
    </script>
    <script src="http://localhost:3000/null"></script>
    <script src="http://localhost:3000/slow" async></script>
    <script src="http://localhost:3000/fast" async></script>
</head>
<body>
    <h1>我出现了!</h1>
</body>
</html>

现在打开index.html:

可以发现此时的slow又打印到了fast前面去了,观察瀑布图和ID号,可以看出来:优先级高的null资源加载完成之后并没有关闭TCP连接,而是使用这个连接继续去加载fast资源。所以现在fast资源想要在slow之前执行,那它的耗时必须将null资源的耗时加上还要小于slow资源的耗时才可以。将slow资源的后端路由修改为:

js 复制代码
app.get('/slow',(req,res)=>{
    setTimeout( () => {
        const fileContent = fs.readFileSync(path.join(__dirname, './slow.js'));
        res.end(fileContent);  
    } , 4000)
})

此时的结果为:

显然由于1+2<4所以fast打印在了slow前面。

将slow资源的后端路由改成2500ms:

4.5 测试5 -- 禁止长连接

在后端设置响应头: res.setHeader('Connection', 'close'); 那么响应报文中的

就会变成

同时不会出现ID复用的情况了(但是瀑布图还是一样的,非常费解!)

4.6 测试6 -- 使用prefetch提前加载下一页的资源

首先将index.html的内容修改成如下的样子:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="prefetch" href="http://localhost:3000/fast">
</head>
<body>
    <h1>我出现了!</h1>
    <button id="btn">点我翻页</button>
    <script>
        const btn = document.getElementById('btn');
        btn.addEventListener('click', ()=>{
            const str = '<script src="http:\/\/localhost:3000\/fast"\>\<\/script\>'
            document.write(str);
        })
    </script>
</body>
</html>

然后打开index.html,可以发现,此时打印台上并没有打印任何内容,也就是说预加载的资源不会在本页面中执行!

注意此时的优先级是"最低",并且查看预览和响应,发现什么内容都没有。

这个时候点击按钮(点击之前记得将 禁用缓存 取消掉)

点击之后,会立刻打印出fast,不必等待两秒:

注意此时的优先级为" ",并且大小一列中,显示的是:预提取缓存;没有ID号,证明没有发送任何的网络请求。

但是prefetch不可乱用,因为可能造成重复加载的问题!

4.6.1 问题一:

将index.html修改成如下所示:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="http://localhost:3000/fast"></script>
    <link rel="prefetch" href="http://localhost:3000/fast">
</head>
<body>
    <h1>我出现了!</h1>
</body>
</html>

那么fast资源一定会被请求两次:

4.6.2 问题二:

prefetch只能加载下一页的资源,下一页的下一页是不会享受这次预加载的:

将index.html修改成下面的内容:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="prefetch" href="http://localhost:3000/fast">
</head>
<body>
    <h1>我出现了!</h1>
    <button id="btn">点我翻页</button>
    <script>
        const btn = document.getElementById('btn');
        btn.addEventListener('click', ()=>{
            const str = `
            \<head\>
                \<script src="http:\/\/localhost:3000\/fast"\>\<\/script\>
            \<\/head\>
            \<body\>
                \<button id="btn2"\>点我继续翻页\<\/button\>
                \<script\>
                    const btn2 = document.getElementById('btn2');
                    btn2.addEventListener('click', ()=>{window.location.href = 'http:\/\/127.0.0.1:5500\/index2.html'})
                \<\/script\>
            \<\/body\>
            `
            document.write(str);
        })
    </script>
</body>
</html>

在相同的目录下面创建一个index2.html文件,其内容为:

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script rel="prefetch" src="http://localhost:3000/fast"></script>
</head>
<body>
    <h1>我又出现了!</h1>
</body>
</html>

现在打开index.html,点击"点我翻页"按钮之后发现prefetch有效,fast瞬间被打印出来;然而点击"点我继续翻页"的时候,发现重新请求了资源!

所以上面的结论是可信的!

如果将index.html的内容改成如下所示,那么点击按钮跳转之后发现prefetch还是生效了的,所以进一步验证了prefetch只有一步的作用!

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="prefetch" href="http://localhost:3000/fast">
</head>
<body>
    <h1>我出现了!</h1>
    <button id="btn">点我跳转到index2.html</button>
    <script>
        const btn = document.getElementById('btn');
        btn.addEventListener('click', ()=>{
        window.location.href = 'http:\/\/127.0.0.1:5500\/index2.html'
        })
    </script>
</body>
</html>
相关推荐
GIS之路7 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug10 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213812 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中34 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路37 分钟前
GDAL 实现矢量合并
前端
hxjhnct40 分钟前
React useContext的缺陷
前端·react.js·前端框架
冰暮流星1 小时前
javascript逻辑运算符
开发语言·javascript·ecmascript
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
前端工作日常1 小时前
我学习到的AG-UI的概念
前端