乐趣区

关于前端:不废话代码实践带你掌握-强缓存协商缓存

前言

大家好,我是林三心,用最通俗易懂的话讲最难的知识点 是我的座右铭,根底是进阶的前提 是我的初心

背景

无论是开发中或者是面试中,HTTP 缓存 都是十分重要的,这体现在了两个方面:

  • 开发中 :正当利用 HTTP 缓存 能够进步前端页面的性能
  • 面试中 HTTP 缓存 是面试中的高频问点

所以本篇文章,我不讲废话,我就通过 Nodejs 的简略实际,给大家讲最通俗易懂的HTTP 缓存,大家通过这篇文章肯定能理解把握它!!!

前置筹备

筹备

  • 创立文件夹 cache-study ,并筹备环境

    npm init
  • 装置 Koa、nodemon

    npm i koa -D
    npm i nodemon -g
  • 创立 index.js、index.html、static 文件夹
  • 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="stylesheet" href="./static/css/index.css">
    </head>
    <body>
    <div class="box">
    
    </div>
    </body>
    </html>
  • static/css/index.css

    .box {
    width: 500px;
    height: 300px;
    background-image: url('../image/guang.jpg');
    background-size: 100% 100%;
    color: #000;
    
    }
  • static/image/guang.jpg

  • index.js

    const Koa = require('koa')
    const fs = require('fs')
    const path = require('path')
    const mimes = {
    css: 'text/css',
    less: 'text/css',
    gif: 'image/gif',
    html: 'text/html',
    ico: 'image/x-icon',
    jpeg: 'image/jpeg',
    jpg: 'image/jpeg',
    js: 'text/javascript',
    json: 'application/json',
    pdf: 'application/pdf',
    png: 'image/png',
    svg: 'image/svg+xml',
    swf: 'application/x-shockwave-flash',
    tiff: 'image/tiff',
    txt: 'text/plain',
    wav: 'audio/x-wav',
    wma: 'audio/x-ms-wma',
    wmv: 'video/x-ms-wmv',
    xml: 'text/xml',
    }
    
    // 获取文件的类型
    function parseMime(url) {
    // path.extname 获取门路中文件的后缀名
    let extName = path.extname(url)
    extName = extName ? extName.slice(1) : 'unknown'
    return mimes[extName]
    }
    
    // 将文件转成传输所需格局
    const parseStatic = (dir) => {return new Promise((resolve) => {resolve(fs.readFileSync(dir), 'binary')
    })
    }
    
    const app = new Koa()
    
    app.use(async (ctx) => {
    const url = ctx.request.url
    if (url === '/') {
      // 拜访根门路返回 index.html
      ctx.set('Content-Type', 'text/html')
      ctx.body = await parseStatic('./index.html')
    } else {const filePath = path.resolve(__dirname, `.${url}`)
      // 设置类型
      ctx.set('Content-Type', parseMime(url))
      // 设置传输
      ctx.body = await parseStatic(filePath)
    }
    })
    
    app.listen(9898, () => {console.log('start at port 9898')
    })

    启动页面

    当初你能够在终端中输出 nodemon index ,看到下方的显示,则代表胜利启动了服务

此时你能够在浏览器链接里输出 http://localhost:9898/ ,关上看到如下页面,则代表页面拜访胜利!!!

HTTP 缓存品种

HTTP 缓存 常见的有两类:

  • 强缓存 :能够由这两个字段其中一个决定

    • expires
    • cache-control(优先级更高)
  • 协商缓存 :能够由这两对字段中的一对决定

    • Last-Modified,If-Modified-Since
    • Etag,If-None-Match(优先级更高)

强缓存

接下来咱们就开始讲 强缓存

expires

咱们只需设置响应头里 expires 的工夫为 以后工夫 + 30s 就行了

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 拜访根门路返回 index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {const filePath = path.resolve(__dirname, `.${url}`)
    // 设置类型
    ctx.set('Content-Type', parseMime(url))
    // 设置 Expires 响应头
    const time = new Date(Date.now() + 30000).toUTCString()
    ctx.set('Expires', time)
    // 设置传输
    ctx.body = await parseStatic(filePath)
  }
})

而后咱们在前端页面刷新,咱们能够看到申请的资源的响应头里多了一个 expires 的字段

并且,在 30s 内,咱们刷新之后,看到申请都是走 memory ,这意味着,通过 expires 设置强缓存的时效是 30s,这 30s 之内,资源都会走本地缓存,而不会从新申请

留神点:有时候你 Nodejs 代码更新了时效工夫,然而发现前端页面还是在走上一次代码的时效,这个时候,你能够把这个 Disabled cache 打钩,而后刷新一下,再勾销打钩

cache-control

其实 cache-control expires 成果差不多,只不过这两个字段设置的值不一样而已,前者设置的是 秒数 ,后者设置的是 毫秒数

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 拜访根门路返回 index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {const filePath = path.resolve(__dirname, `.${url}`)
    // 设置类型
    ctx.set('Content-Type', parseMime(url))
    // 设置 Cache-Control 响应头
    ctx.set('Cache-Control', 'max-age=30')
    // 设置传输
    ctx.body = await parseStatic(filePath)
  }
})

前端页面响应头多了 cache-control 这个字段,且 30s 内都走本地缓存,不会去申请服务端

协商缓存

强缓存 不同的是, 强缓存 是在时效工夫内,不走服务端,只走本地缓存;而 协商缓存 是要走服务端的,如果申请某个资源,去申请服务端时,发现 命中缓存 则返回 304 ,否则则返回所申请的资源,那怎么才算 命中缓存 呢?接下来讲讲

Last-Modified,If-Modified-Since

简略来说就是:

  • 第一次申请资源时,服务端会把所申请的资源的 最初一次批改工夫 当成响应头中 Last-Modified 的值发到浏览器并在浏览器存起来
  • 第二次申请资源时,浏览器会把刚刚存储的工夫当成申请头中 If-Modified-Since 的值,传到服务端,服务端拿到这个工夫跟所申请的资源的最初批改工夫进行比对
  • 比对后果如果两个工夫雷同,则阐明此资源没批改过,那就是 命中缓存 ,那就返回 304 ,如果不雷同,则阐明此资源批改过了,则 没命中缓存 ,则返回批改过后的新资源
// 获取文件信息
const getFileStat = (path) => {return new Promise((resolve) => {fs.stat(path, (_, stat) => {resolve(stat)
    })
  })
}

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 拜访根门路返回 index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {const filePath = path.resolve(__dirname, `.${url}`)
    const ifModifiedSince = ctx.request.header['if-modified-since']
    const fileStat = await getFileStat(filePath)
    console.log(new Date(fileStat.mtime).getTime())
    ctx.set('Cache-Control', 'no-cache')
    ctx.set('Content-Type', parseMime(url))
    // 比对工夫,mtime 为文件最初批改工夫
    if (ifModifiedSince === fileStat.mtime.toGMTString()) {ctx.status = 304} else {ctx.set('Last-Modified', fileStat.mtime.toGMTString())
      ctx.body = await parseStatic(filePath)
    }
  }
})

第一次申请时,响应头中:

第二次申请时,申请头中:

因为资源并没批改,则命中缓存,返回 304:

此时咱们批改一下 index.css

.box {
  width: 500px;
  height: 300px;
  background-image: url('../image/guang.jpg');
  background-size: 100% 100%;
  /* 批改这里 */
  color: #333;
}

而后咱们刷新一下页面, index.css 变了,所以会 没命中缓存 ,返回 200 和新资源,而 guang.jpg 并没有批改,则 命中缓存 返回 304:

Etag,If-None-Match

其实 Etag,If-None-Match Last-Modified,If-Modified-Since 大体一样,区别在于:

  • 后者是比照资源最初一次批改工夫,来确定资源是否批改了
  • 前者是比照资源内容,来确定资源是否批改

那咱们要怎么比对资源内容呢?咱们只须要读取资源内容,转成 hash 值,前后进行比对就行了!!

const crypto = require('crypto')

app.use(async (ctx) => {
  const url = ctx.request.url
  if (url === '/') {
    // 拜访根门路返回 index.html
    ctx.set('Content-Type', 'text/html')
    ctx.body = await parseStatic('./index.html')
  } else {const filePath = path.resolve(__dirname, `.${url}`)
    const fileBuffer = await parseStatic(filePath)
    const ifNoneMatch = ctx.request.header['if-none-match']
    // 生产内容 hash 值
    const hash = crypto.createHash('md5')
    hash.update(fileBuffer)
    const etag = `"${hash.digest('hex')}"`
    ctx.set('Cache-Control', 'no-cache')
    ctx.set('Content-Type', parseMime(url))
    // 比照 hash 值
    if (ifNoneMatch === etag) {ctx.status = 304} else {ctx.set('etag', etag)
      ctx.body = fileBuffer
    }
  }
})

验证形式跟刚刚 Last-Modified,If-Modified-Since 的一样,这里就不反复阐明了。。。

总结

参考资料

  • https://blog.csdn.net/qq_3243…

结语

我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜爱前端,想学习前端,那咱们能够交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】

退出移动版