前端缓存最佳实践

article/2025/3/14 10:46:07

点击上方“前端开发博客”,选择“设为星标”

回复“2”加入前端群

作者:黑金团队
https://juejin.cn/post/6844903737538920462

前言

缓存,这是一个老生常谈的话题,也常被作为前端面试的一个知识点。本文,重点在与探讨在实际项目中,如何进行缓存的设置,并给出一个较为合理的方案。

强缓存和协商缓存

在介绍缓存的时候,我们习惯将缓存分为强缓存和协商缓存两种。两者的主要区别是使用本地缓存的时候,是否需要向服务器验证本地缓存是否依旧有效。顾名思义,协商缓存,就是需要和服务器进行协商,最终确定是否使用本地缓存。

两种缓存方案的问题点

强缓存

我们知道,强缓存主要是通过http请求头中的Cache-Control和Expire两个字段控制。Expire是HTTP1.0标准下的字段,在这里我们可以忽略。我们重点来讨论的Cache-Control这个字段。一般,我们会设置Cache-Control的值为“public, max-age=xxx”,表示在xxx秒内再次访问该资源,均使用本地的缓存,不再向服务器发起请求。显而易见,如果在xxx秒内,服务器上面的资源更新了,客户端在没有强制刷新的情况下,看到的内容还是旧的。如果说你不着急,可以接受这样的,那是不是完美?然而,很多时候不是你想的那么简单的,如果发布新版本的时候,后台接口也同步更新了,那就gg了。有缓存的用户还在使用旧接口,而那个接口已经被后台干掉了。怎么办?

协商缓存

协商缓存最大的问题就是每次都要向服务器验证一下缓存的有效性,似乎看起来很省事,不管那么多,你都要问一下我是否有效。但是,对于一个有追求的码农,这是不能接受的。每次都去请求服务器,那要缓存还有什么意义。

最佳实践

缓存的意义就在于减少请求,更多地使用本地的资源,给用户更好的体验的同时,也减轻服务器压力。所以,最佳实践,就应该是尽可能命中强缓存,同时,能在更新版本的时候让客户端的缓存失效。在更新版本之后,如何让用户第一时间使用最新的资源文件呢?机智的前端们想出了一个方法,在更新版本的时候,顺便把静态资源的路径改了,这样,就相当于第一次访问这些资源,就不会存在缓存的问题了。

伟大的webpack可以让我们在打包的时候,在文件的命名上带上hash值。

entry:{main: path.join(__dirname,'./main.js'),vendor: ['react', 'antd']
},
output:{path:path.join(__dirname,'./dist'),publicPath: '/dist/',filname: 'bundle.[chunkhash].js'
}
复制代码

综上所述,我们可以得出一个较为合理的缓存方案:

  • HTML:使用协商缓存。

  • CSS&JS&图片:使用强缓存,文件命名带上hash值。

哈希也有讲究

webpack给我们提供了三种哈希值计算方式,分别是hash、chunkhash和contenthash。那么这三者有什么区别呢?

  • hash:跟整个项目的构建相关,构建生成的文件hash值都是一样的,只要项目里有文件更改,整个项目构建的hash值都会更改。

  • chunkhash:根据不同的入口文件(Entry)进行依赖文件解析、构建对应的chunk,生成对应的hash值。

  • contenthash:由文件内容产生的hash值,内容不同产生的contenthash值也不一样。

显然,我们是不会使用第一种的。改了一个文件,打包之后,其他文件的hash都变了,缓存自然都失效了。这不是我们想要的。那chunkhash和contenthash的主要应用场景是什么呢?在实际在项目中,我们一般会把项目中的css都抽离出对应的css文件来加以引用。如果我们使用chunkhash,当我们改了css代码之后,会发现css文件hash值改变的同时,js文件的hash值也会改变。这时候,contenthash就派上用场了。

ETag计算

Nginx

Nginx官方默认的ETag计算方式是为"文件最后修改时间16进制-文件长度16进制"。例:ETag:“59e72c84-2404”

Express

Express框架使用了serve-static中间件来配置缓存方案,其中,使用了一个叫etag的npm包来实现etag计算。从其源码可以看出,有两种计算方式:

  • 方式一:使用文件大小和修改时间

function stattag (stat) {var mtime = stat.mtime.getTime().toString(16)var size = stat.size.toString(16)return '"' + size + '-' + mtime + '"'
}
复制代码
  • 方式二:使用文件内容的hash值和内容长度

function entitytag (entity) {if (entity.length === 0) {// fast-path emptyreturn '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'}// compute hash of entityvar hash = crypto.createHash('sha1').update(entity, 'utf8').digest('base64').substring(0, 27)// compute length of entityvar len = typeof entity === 'string'? Buffer.byteLength(entity, 'utf8'): entity.lengthreturn '"' + len.toString(16) + '-' + hash + '"'
}
复制代码

ETag与Last-Modified谁优先

协商缓存,有ETag和Last-Modified两个字段。那当这两个字段同时存在的时候,会优先以哪个为准呢?在Express中,使用了fresh这个包来判断是否是最新的资源。主要源码如下:

function fresh (reqHeaders, resHeaders) {// fieldsvar modifiedSince = reqHeaders['if-modified-since']var noneMatch = reqHeaders['if-none-match']// unconditional requestif (!modifiedSince && !noneMatch) {return false}// Always return stale when Cache-Control: no-cache// to support end-to-end reload requests// https://tools.ietf.org/html/rfc2616#p-14.9.4var cacheControl = reqHeaders['cache-control']if (cacheControl && CACHE_CONTROL_NO_CACHE_REGEXP.test(cacheControl)) {return false}// if-none-matchif (noneMatch && noneMatch !== '*') {var etag = resHeaders['etag']if (!etag) {return false}var etagStale = truevar matches = parseTokenList(noneMatch)for (var i = 0; i < matches.length; i++) {var match = matches[i]if (match === etag || match === 'W/' + etag || 'W/' + match === etag) {etagStale = falsebreak}}if (etagStale) {return false}}// if-modified-sinceif (modifiedSince) {var lastModified = resHeaders['last-modified']var modifiedStale = !lastModified || !(parseHttpDate(lastModified) <= parseHttpDate(modifiedSince))if (modifiedStale) {return false}}return true
}
复制代码

我们可以看到,如果不是强制刷新,而且请求头带上了if-modified-since和if-none-match两个字段,则先判断etag,再判断last-modified。当然,如果你不喜欢这种策略,也可以自己实现一个。

补充:后端需要怎么设置

上文主要说的是前端如何进行打包,那后端怎么做呢?我们知道,浏览器是根据响应头的相关字段来决定缓存的方案的。所以,后端的关键就在于,根据不同的请求返回对应的缓存字段。以nodejs为例,如果需要浏览器强缓存,我们可以这样设置:

res.setHeader('Cache-Control', 'public, max-age=xxx');
复制代码

如果需要协商缓存,则可以这样设置:

res.setHeader('Cache-Control', 'public, max-age=0');
res.setHeader('Last-Modified', xxx);
res.setHeader('ETag', xxx);
复制代码

当然,现在已经有很多现成的库可以让我们很方便地去配置这些东西。写了一个简单的demo,方便有需要的朋友去了解其中的原理,有兴趣的可以阅读源码

总结

在做前端缓存时,我们尽可能设置长时间的强缓存,通过文件名加hash的方式来做版本更新。在代码分包的时候,应该将一些不常变的公共库独立打包出来,使其能够更持久的缓存。以上,如有错漏,欢迎指正!

@Author: TDGarden

推荐阅读

前端应该学习的 Token 登录认证知识

END

关注下方「前端开发博客」,回复 “简历模板”

领取33个精选前端简历模板

❤️ 看完两件事

如果你觉得这篇内容对你挺有启发,我想邀请你帮我两个小忙:

  1. 点个「在看」,让更多的人也能看到这篇内容(喜欢不点在看,都是耍流氓 -_-)

  2. 关注公众号「前端开发博客」,每周重点攻克一个前端面试重难点

如果觉得这篇文章还不错,来个【分享、点赞、在看】三连吧,让更多的人也看到~

公众号也开始通过互动率推送了,互动少了可能就很晚或者收不到文章了。

大家点个在看,星标我的公众号,就可以及时获得推文。

点个在看少个Bug


http://chatgpt.dhexx.cn/article/kZvdK3yG.shtml

相关文章

技术点:前端缓存分类及使用

前端缓存 什么是 web 缓存&#xff08;前端缓存&#xff09; web 缓存主要指的是两部分&#xff1a;浏览器缓存和 http 缓存 浏览器缓存&#xff1a;比如,localStorage,sessionStorage,cookie 等等。这些功能主要用于缓存一些必要的数据&#xff0c;比如用户信息。比如需要携…

前端缓存详解

目录 前言 一、按缓存位置分类 HTTP状态码及区别 几种状态的执行顺序 Memory Cache Disk Cache Service Worker 请求网络 二、HTTP 缓存 HTTP 缓存分类 强缓存原理 协商缓存原理 更新和废弃缓存 三、缓存小结 四、缓存的优点 五、浏览器缓存策略 五、缓存的应…

一文!彻底弄懂前端缓存

前端缓存 前端缓存&#xff0c;这是一个老生常谈的话题&#xff0c;也常被作为前端面试的一个知识点。今天我们再来总结一下。 分类 前端缓存分为强缓存和协商缓存两种。 强缓存 强缓存主要使用Expires、Cache-Control 两个头字段&#xff0c;两者同时存在Cache-Control 优先级…

【前端页面缓存技术方案】

前端页面缓存技术方案 关于页面缓存数据的纯前端技术方案背景项目存在的现有方案思考&#x1f914;其他技术调研react-activationreact-router-cache-route 结论 关于页面缓存数据的纯前端技术方案 背景 为了优化用户的体验&#xff0c;可能会遇到这样的需求&#xff1a;在列…

前端常用缓存技术

http://www.cnblogs.com/belove8013/p/8134067.html 今天刚上班就听到群里的几位大佬在讨论所开发的系统需要重复的登录的恶心之处&#xff0c;听各位大佬争辩的同时&#xff0c;想到了自己以前整理过的缓存技术&#xff0c;算是比较全面的&#xff0c;当然了只是帮助自己理解的…

我的网站心得之缓存技术(前端篇)

在前端面试中&#xff0c;storage是面试官经常问的问题&#xff0c;我先问你几个问题&#xff0c;如果你回答不上来&#xff0c;那么你应该阅读一下&#xff1a;知道storage吗&#xff1f;storage存储的数据类型有什么&#xff1f;sessionStorage的生命周期&#xff1f;你都用l…

中高级前端工程师都需要熟悉的技能--前端缓存

前言 web缓存是高级前端工程师必修技能。是我们变成大牛过程中绕不开的知识点。 文章会尽量用通俗易懂的言语来细说web缓存的概念和用处。 本期文章的大纲是 什么是web缓存&#xff08;前端缓存&#xff09; 缓存可以解决什么问题&#xff1f;他的缺点是什么&#xff1f; …

ovo svm_反思我在OVO担任远程产品设计实习生的时间

ovo svm In a quiet bedroom accompanied only by the low humming of my laptop fan, I sat before a Google Hangouts meeting, and got to know my colleagues for the first time, unaware of the joy of a ride that was waiting for me at OVO Design. 在一个安静的卧室里…

反思最近这些时日的荒废

为什么80%的码农都做不了架构师&#xff1f;>>> 算是一时兴起&#xff0c;最近lol排位已经将自己的折磨的不成人样。闲了这么久&#xff0c;是时候找份工作了。最近一直没敢跟家里人打电话&#xff0c;实在不知道该说些什么&#xff0c;一开口便是谎言。是否自己真的…

团队愿景_周一的愿景,每日的成果,周五的远程团队管理反思

团队愿景 My friend J.D. Meier has an amazing blog called Sources of Insight and hes written a fantastic book called Getting Results the Agile Way. You can buy his book on Amazon (its free on Kindle Unlimited!). I put J.D. up there with David Allen and Step…

WPBeginner年满10岁-反思,更新和WordPress赠品(奖金124,000美元以上)

Wow, it’s the tenth fourth. Today, WPBeginner is officially 10 years old — feels unreal to type this! 哇&#xff0c;是第十位 今天&#xff0c;WPBeginner正式成立了10岁-键入此图标感到不切实际&#xff01; Like every year, I want to take a few minutes and d…

误泄露公司代码、疫情期间被裁,一个“菜鸟”程序员的生存日记

作者 | Adam Hughes 译者 | Sambodhi 策划 | Tina 编辑&#xff5c;燕珊 “我是如何从每一次失败中成长起来的。” 身为程序员&#xff0c;我们往往都了解大神级程序员的故事。比如很小就开始编程&#xff0c;在 11 岁时就创建了第一家能盈利的网站&#xff0c;16 岁上大学、17…

失败需要反思

2019独角兽企业重金招聘Python工程师标准>>> 公司的第一款游戏是抄袭的COC&#xff0c;然而我加入进去的时候开发已过半&#xff0c;进入之后主要是参与一些新的系统与玩法的开发&#xff0c;在我加入到这个项目之后还开发了大概半年时间&#xff0c;据说此前已经开…

CTF笔记 个人HNCTF反思(部分题目)

文章目录 [WEEK2]easy_include自己的胡思乱想WP [WEEK2]easy_unserexp [WEEK2]easy_sqlWP [WEEK2]ez_SSTIPAYLOAD [WEEK4]pop子和pipi美总结 怎么说呢&#xff0c;这次充分感觉到了自己的无能&#xff0c;可能因为在比赛马上结束的时候加入&#xff0c;让我没心思慢慢思考&…

一名大学毕业生的反思_反思我大学毕业时的软件工程师的第一年

一名大学毕业生的反思 Note: This post is mainly targeted towards students who are about to graduate or have already graduated and are preparing to start their new full-time job. Some of the examples used are specific to my experience as a New Grad Software …

华清远见嵌入式培训_第二周回顾与反思

目录 前言 周一 一、switch..case 1.1 注意事项 1.2 使用练习 二、循环控制语句 2.1 使用goto实现循环 2.2 while循环 2.3 do..while 循环 2.4 for 循环 2.5 死循环 2.6 辅助控制关键字 周二 一、数组 1.1 一维数组 1.2 数组越界问题 1.3 二维数组 1.4 编码练…

让计算机开口说话教学反思,英语教学反思(合集15篇)

英语教学反思(合集15篇) 身为一位优秀的老师&#xff0c;课堂教学是我们的工作之一&#xff0c;借助教学反思我们可以快速提升自己的教学能力&#xff0c;我们该怎么去写教学反思呢&#xff1f;以下是小编为大家收集的英语教学反思&#xff0c;希望能够帮助到大家。 英语教学反…

华清远见嵌入式培训_第五周回顾与反思

前言 这是在华清学习的第五个周末&#xff0c;这一周主要学习的是数据结构。老师说数据结构是一门非常庞大的学科&#xff0c;单单是数据结构里的一个小分支&#xff0c;单拎出来一周都未必可以学透&#xff0c;因此这周的数据结构课程里更多的是思维方向的学习&#xff0c;学习…

利用Python识别txt文本并根据其内容进行文件分类

事情是这样的&#xff0c;有一个图片数据集需要根据分成很多类以便于给其设置标签&#xff0c;但所有的图片都在一个文件里&#xff0c;另外又给了个.txt文件&#xff0c;其中每行都是对应图片的类别。例如第1行对应的第0001.jpg是第14类&#xff08;每个类都有多张图片&#x…

C# 批量修改文件名称

目的 对文件夹中所有文件名实现批量修改&#xff08;添加新字符&#xff09; 思路 获取文件夹路径获取文件夹中所有文件的文件名对文件名进行批量修改 方法 窗口设计 获取文件夹路径 使用FolderBrowserDialog控件获取文件夹路径&#xff0c;并用Directory.Exists方法对路径…