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

526 阅读5分钟

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

1. 测试用的项目构建

yarn init - y
yarn add express
touch fast.js
touch slow.js
touch null.js
touch index.html

往index.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中使用到的三个脚本文件的内容分别为:

console.log('白屏1秒');
console.log('fast')
console.log('slow')

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

3. 后端服务器构造

显然需要一个都唔起,这里使用express提供服务:touch server.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:

  • 修改之前:

image.png

  • 修改之后:

image.png

  • 对比一下:
    • 改变的点有: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中改成

<!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:

image.png

image.png

image.png

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

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

image.png

image.png

image.png

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

4.3.2 将index.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:

image.png

image.png

image.png

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

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

再打开html就会发现:

image.png

image.png

image.png

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

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

4.4 测试4 -- async混用

将index.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:

image.png

image.png

image.png

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

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

此时的结果为:

image.png

image.png

image.png

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

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

image.png

image.png

image.png

4.5 测试5 -- 禁止长连接

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

image.png

就会变成

image.png

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

image.png

image.png

image.png

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

首先将index.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,可以发现,此时打印台上并没有打印任何内容,也就是说预加载的资源不会在本页面中执行!

image.png

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

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

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

image.png

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

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

4.6.1 问题一:

将index.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资源一定会被请求两次:

image.png

4.6.2 问题二:

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

将index.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文件,其内容为:

<!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只有一步的作用!

<!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>