前言
我们打开百度这个网站并刷新多次时时,注意到百度的logo是没有每次都加载一遍的。我们知道图片是img标签中的src属性加载出来的,这也需要浏览器去请求图片资源的,那么为什么刷新多次浏览器只请求了一次图片资源呢?这就涉及到了浏览器的缓存策略了,这张图片被浏览器缓存下来了!
正文
一、为什么要有浏览器的缓存策略?
- 提升用户体验,减少页面重复的http请求
二、为什么通过浏览器url地址栏访问的html页面不缓存?
- 当 强制刷新页面 或 浏览器url地址栏访问资源 时,浏览器
默认
会在请求头
中设置Cache-control: no-cache
,如有
设置该属性浏览器就会忽略
响应头中的 Cache-control
如何优化网络资源请求的时间呢?有以下三种方式。
三、CDN网络分发
CDN:CDN会通过负载均衡技术,将用户的请求定向到最合适的缓存服务器上去获取内容。
比如说,北京的用户,我们让他访问北京的节点,深圳的用户,我们让他访问深圳的节点。通过就近访问,加速用户对网站的访问,进而解决Internet网络拥堵状况,提高用户访问网络的响应速度。
四、强缓存
强缓存是浏览器的缓存策略,后端设置响应头中的属性值就能设置文件资源在浏览器的缓存时间,过了缓存的有效期再次访问时,文件资源需再次加载。
强缓存有两种方式来控制资源被浏览器缓存的时长:
- 后端设置响应头中的 Cache-control: max-age=3600 来控制缓存时长(为一个小时)
- 后端设置响应头中的 Expires:xxx 来控制缓存的截止日期(截止日期为xxx)
我们直接上代码让你更好理解,我们需要实现一个页面,页面上需展现一个标题及一张图片:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Earth</h1>
<img src="assets/image/earth.jpeg" >
</body>
</html>
const http = require('http');
const path = require('path');
const fs = require('fs');
const mime = require('mime'); //接收一个文件后缀,返回一个文件类型
const server = http.createServer((req, res) => {
const filePath = path.resolve(__dirname, `www/${req.url}`) //resolve合并地址
if (fs.existsSync(filePath)) { //判断路径是否有效
const stats = fs.statSync(filePath) //获取文件信息
const isDir = stats.isDirectory() //是否是文件夹
if (isDir) {
filePath = path.join(filePath, 'index.html')
}
//读取文件
if (!isDir || fs.existsSync(filePath)) {
//判断前端请求的路径的后缀名是图片还是文本
const { ext } = path.parse(filePath) //.html .jpeg
const time = new Date(Date.now() + 3600000).toUTCString() //定义时间 作为缓存时间的有效期
let status = 200
res.writeHead(status, {
'Content-Type': `${mime.getType(ext)};charset=utf-8`,
'Cache-control': 'max-age=3600', //缓存时长为一小时
// 'expires': time //截止日期 缓存一小时后过期
})
if (status === 200) {
const fileStream = fs.createReadStream(filePath) //将文件读成流类型
fileStream.pipe(res) //将文件流导入响应体
}else{
res.end();
}
}
}
})
server.listen(3000, () => {
console.log('listening on port 3000');
})
第一次运行:
刷新页面后,可以看到图片资源没有重新加载:
三、协商缓存
我们想象这样的场景:当我们偷偷把图片偷偷换成另一张图片,图片名依然和之前那张一样,会是什么结果呢?
操作后,刷新页面发现图片还是之前那张图片,并没有换成新的!那这就出事儿了,后端图片换了,用户看到的还是老图片,有一种方案是改变图片资源的名字,直接请求最新图片资源,但这并不是最优方案,终极方案是需要协商缓存的帮忙。
协商缓存也是浏览器的缓存策略,它也有两种方式辅助强缓存,来判断文件资源是否被修改:
1. 后端设置响应头中的 last-modified: xxxx
- 辅助强缓存,让URL地址栏请求的资源也能被缓存
- 辅助强缓存,借助请求头中的if-modified-since来判断资源文件是否被修改,如果被修改则返回新的资源,否则返回304状态码,让前端读取本地缓存
代码如下:
const http = require('http');
const path = require('path');
const fs = require('fs');
const mime = require('mime'); //接收一个文件后缀,返回一个文件类型
const server = http.createServer((req, res) => {
const filePath = path.resolve(__dirname, `www/${req.url}`) //resolve合并地址
if (fs.existsSync(filePath)) { //判断路径是否有效
const stats = fs.statSync(filePath) //获取文件信息
const isDir = stats.isDirectory() //是否是文件夹
if (isDir) {
filePath = path.join(filePath, 'index.html')
}
//读取文件
if (!isDir || fs.existsSync(filePath)) {
//判断前端请求的路径的后缀名是图片还是文本
const { ext } = path.parse(filePath) //.html .jpeg
const time = new Date(Date.now() + 3600000).toUTCString() //定义时间 作为缓存时间的有效期
const timeStamp = req.headers['if-modified-since'] //请求头的if-modified-since字段
let status = 200
//判断文件是否修改过
if (timeStamp && Number(timeStamp) === stats.mtimeMs) { //timeStamp为字符串 转换为number类型判断
status = 304
}
res.writeHead(status, {
'Content-Type': `${mime.getType(ext)};charset=utf-8`,
'Cache-control': 'max-age=3600', //缓存时长为一小时 //max-age=0或no-cache不需要缓存
// 'expires': time //截止日期 缓存一小时后过期
'last-modified': stats.mtimeMs //文件最后一次修改时间
})
if (status === 200) {
const fileStream = fs.createReadStream(filePath) //将文件读成流类型
fileStream.pipe(res) //将文件流导入响应体
}else{
res.end();
}
}
}
})
server.listen(3000, () => {
console.log('listening on port 3000');
})
我们只要看last-modified这个字段的值有无变化即可:
2. Etag:文件的标签
- 请求头中会被携带If-None-Match
- Etag保证了每一个资源是唯一的,资源变化都会导致Etag变化。服务器根据If-None-Match值来判断是否命中缓存。 当服务器返回304的响应时,由于ETag重新生成过,response header中还会把这个ETag返回,即使这个ETag跟之前的没有变化。
代码如下:
const http = require('http');
const path = require('path');
const fs = require('fs');
const mime = require('mime'); //接收一个文件后缀,返回一个文件类型
const md5 = require('crypto-js/md5');
const server = http.createServer((req, res) => {
const filePath = path.resolve(__dirname, `www/${req.url}`) //resolve合并地址
if (fs.existsSync(filePath)) { //判断路径是否有效
const stats = fs.statSync(filePath) //获取文件信息
const isDir = stats.isDirectory() //是否是文件夹
if (isDir) {
filePath = path.join(filePath, 'index.html')
}
//读取文件
if (!isDir || fs.existsSync(filePath)) {
//判断前端请求的路径的后缀名是图片还是文本
const { ext } = path.parse(filePath) //.html .jpeg
const content = fs.readFileSync(filePath);
let status = 200
//判断文件是否被修改过
if (req.headers['if-none-match'] == md5(content)) {
status=304
}
res.writeHead(status, {
'Content-Type': `${mime.getType(ext)};charset=utf-8`,
'Cache-control': 'max-age=3600', //缓存时长为一小时 //max-age=0或no-cache不需要缓存
'Etag': md5(content) //文件资源的md5值
})
if (status === 200) {
const fileStream = fs.createReadStream(filePath) //将文件读成流类型
fileStream.pipe(res) //将文件流导入响应体
} else {
res.end();
}
}
}
})
server.listen(3000, () => {
console.log('listening on port 3000');
})
结
最后附上一张图便于更好理解浏览器的缓存策略:
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END