经典面试之浏览器的缓存策略

550 阅读4分钟

定义

浏览器的缓存机制就是我们所说的HTTP的缓存机制,本质上是为了提升用户体验、提升性能,减少页面重复的HTTP请求,帮助我们优化性能,比如:直接使用缓存不需要再次发起重复请求、减少客户端和服务器之间的请求次数、减少网络的负荷等。

通常浏览器的缓存策略分为两种:强缓存协商缓存。接下来我们用代码演示浏览器的缓存策略。

浏览器缓存策略

环境搭建和代码展示

1.png

创建server文件夹,我们先在终端初始化,再在这个文件夹下创建www文件夹存放index.html文件以及我们需要存放所需图片的文件夹。接下来是准备工作的代码详细展示。

`

//这里是我们需要的引用

const http = require('http')
const path = require('path')
const fs = require('fs')


const server = http.createServer((req, res) => {
let filePath = path.resolve(__dirname, `www/${req.url}`)//合并 创建 

//判断路径是否合法
     if (fs.existsSync(filePath)) {
 
 //获取文件信息
    const stats = fs.statSync(filePath)

//判断是否是文件夹
    const isDir = stats.isDirectory()
//如果是文件夹应该寻找到这个文件夹下的index.html文件
    if (isDir) {
      filePath = path.join(filePath, 'index.html')
}
//如果是一个文件的话直接读取文件的内容
    if (!isDir || fs.existsSync(filePath)) {
    const content = fs.readFileSync(filePath)
  //前端请求的路径的后缀名
    const { ext } = path.parse(filePath)//解析 后缀
  //设置响应头 状态码 
    if (ext === '.jpg') {
     res.writeHead(200, { 'Content-Type': 'image/jpg' })
   } else {
     res.writeHead(200, { 'Content-Type': 'text/html;charset:utf-8' })

   }
  // 将读到的文件资源输出给前端
      return res.end(content)
  
    }
  }
})
  server.listen(3000, () => {
  console.log('Server is listening on port 3000');
})

`

这样,当我们访问3000端口的时候就可以展示index.html中的文本内容和图片内容了。

强缓存:

定义:强缓存就是当客户端向服务器发起第二次相同的资源的时候,不会向服务器发送请求,而是直接从缓存当中读取数据。

方法:通过后端设置响应头中的cache-control为max-age=xxx(time) 或者expires:xxx(time) 来控制资源被浏览器缓存的时间时长。根据这两个响应头字段设置的过期时间,来判断是否需要发送请求还是直接读取缓存数据。下面展示这两种方式的代码详细:

代码展示

`

 res.writeHead(200,
    {
          
          'Cache-control': 'max-age=3600',//缓存一小时
    }

`

Cache-control: 这个字段采用的是过期时长,就是一个时间段来控制缓存而不是具体的某个过期时间。

在设置响应头内添加Cache-control字段,这个字段是以秒为单位,上述设置的表示这个响应返回是在3600秒,一个小时之内可以直接使用缓存而不需要重新发送相同的请求。

`

  //设置截止日期 一个小时
  const time = new Date(Date.now() + 3600000).toUTCString()

  res.writeHead(200,
    {
      'Content-Type': `${mime.getType(ext)};charset=utf-8`,
      //缓存一小时后过期
      'expires':time
  
    }

`

Expires:指的是过期时间,存在于返回的响应头中,这个表示告诉浏览器在这一个过期的时间点之前可以直接从缓存中获取数据,不需要再次发送请求。上述代码中,time表示的是一个具体的过期时间点。

强制刷新页面和通过浏览器url地址栏访问资源

当我们进行cache-control进行强缓存的时候,在时间过期之内,当我们的一些代码图片等资源进行变动同时文件名没改动的时候浏览器展示出来的内容仍旧是我们之前强缓存的内容而不是新的内容,因此我们可以进行强制刷新页面跳过强缓存和协商缓存,直接去请求新的资源,返回的状态码是200 OK。

所以,在我们强制刷新页面和通过浏览器url地址栏访问资源的时候, 默认会在请求头中设置 Cache-control:no-cache ,有该属性,浏览器就会忽略响应头中的cache-control,不进行缓存。

协商缓存:

定义:客户端向服务器发送相同的资源请求时,浏览器在请求头中携带相应的缓存标识tag向服务器发送请求,询问请求的在本地文件缓存和服务器相比较是否进行了更改,如果进行了更改那就更新文件,如果没有进行更改那就直接从缓存当中进行读取。通过设置:Last-ModifiedEtag来实现。

方法

  • 后端设置响应头中的 Last-modified:xxxxx。协商缓存的目的是辅助强缓存,让URL地址栏请求的资源也能被缓存,借助请求头中的if-modified-since 来判断资源文件是否被修改,如果被修改则返回新的资源,否则返回304状态,让前端直接读取本地缓存。

  • Etag:文件的签名 请求头中会被携带的if-none-match

代码展示: `

  const timeStamp = req.headers['if-modified-since']  //读取这个字段
  let status = 200
  //资源没有变动
  if(timeStamp && Number(timeStamp) ===status.mtimeMs){
    status = 304
  }
    res.writeHead(status,
    {
     
      'Cache-control': 'max-age=3600',//缓存一小时
      'Last-Modified': stats.mtimeMs,//文件最后修改时间
    }
    //第一次加载200 第二次加载304
      if (status === 200) {
      const fileStream = fs.createReadStream(filePath)//将文件读取成了一个流类型
      fileStream.pipe(res)

    } else {//304缓存还是没有被修改
        res.end()

  }

` 在上述代码中两个字段分别表示:

Last-Modified:浏览器第一次发送请求给服务器的时候,会在响应头中添加,表示的是文件的最后修改时间,浏览器在接收到之后会进行这个header的缓存。

if-Modified-Since:浏览器再次接收到请求的时候,会在请求头中添加这个字段。根据这个字段去与资源文件的最后修改时间进行对比,如果资源没有新的修改,返回状态码304和res.end()即空的响应体,表示直接从缓存中读取资源。如果资源进行了变动,返回新的资源和状态码200。

`

     if(req.headers['if-none-match'] == md5(content)){
            status = 304
      }

     res.writeHead(status,
    {
          'Cache-control': 'max-age=3600',//缓存一小时
          'Last-Modified': stats.mtimeMs,//文件最后修改时间
          'Etag':md5(content)//文件资源的md5值
    },

  )

`

E-tag:根据资源内容是否出现了变化,来决定缓存策略。

If-None-Match:浏览器在下一次向服务器发送请求的时候,会在请求头里面存放If-None-Match值,这个值是上一次返回的E-tag值,服务器只需要对比请求头的If-None-Match值和服务器上的E-tag值是否相等。如果相等则返回304,直接从本地缓存取值,不相等则返回200状态码。

E-tag和Last-Modified的区别:

  • Last-Modified:是根据文件的修改时间来判断文件是否被修改过,而假设文件修改后又被再次改回为原状态,系统依旧会认定文件是被修改过的,从而导致前端的缓存失效。

  • Etag是根据文件内容制作的签名,不会产生以上问题。