如果将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>