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

article/2025/3/15 12:17:37

在前端面试中,storage是面试官经常问的问题,我先问你几个问题,如果你回答不上来,那么你应该阅读一下:知道storage吗?storage存储的数据类型有什么?sessionStorage的生命周期?你都用localStorage做一些什么事?localStorage容量限制是多大?了解indexedDB跟WebSQL吗?如果你要在前端缓存视频或者图片怎么实现? 

目录

1、使用localStorage:

2、使用sessionStorage:

3、使用Cookie

4、使用IndexedDB跟Web SQL


在我做网站的时候,由于服务器带宽有限(仅仅3M),一开始打开视频页面和音乐页面会非常卡,经常造成接口请求超时,因此必须在前后台都加入缓存机制。其实我们使用的很多软件都有缓存机制,大部分人接触的基本都是后台的缓存中间件redis等等,它可以有效的减轻数据库和服务器的压力。前端缓存主要着重用户体验,实现一些特定功能(如记住上一次浏览),为用户节省不必要的流量浪费,减少QBS带给服务器的压力。本文章主要针对前端的浏览器缓存Storage(localStorage,SessionStorage)、IndexedDB(WebSQL)、Cookie,谈谈我个人的理解和实现,理解可能有误,请多指正。

本文主要内容有:localStorage存储,sessionStorage存储图片对象,IndexedDB存储音乐(实现听过的音乐0流量播放)。

注意:本篇描述的是手动管理缓存,因此不会使用下面给Header添加标签的缓存技术。

<meta http-equiv="Cache-Control" content="public" />
<meta http-equiv="Pragma" content="public" />
<meta http-equiv="Expires" content="36000" />

        这是因为,几乎所有网站都不会开启强缓存,甚至会禁用强制缓存...

<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

        其实,目前浏览器是有自动缓存机制的,它都是存储的静态资源。在Tomcat和JSP的年代,这个自动缓存让前端简直吃屎,甚至手动清理浏览器缓存跟重启Tomcat都不一定清的掉。笔者之前在页面引了一张图片,然后发现不好看,就用另一张图片替换了,结果总是展示之前那张图片。其实在静态资源后面加入时间戳机制或一个方法等可以有效阻止此类缓存,这类机制将导致页面会重新请求静态资源,从而避免了使用缓存的静态资源,如:

<script src="js/zq.js?v=123"></script>
<script src="js/zq.js?time=<%=System.currentTimeMillis();%>"></script>

        这些自动缓存机制我们完全不必在意这些是如何实现的。下面我就介绍以下前台需要我们手动操作的缓存技术及API。

拿我音乐demo页面为例(demo链接):

        首先,Storage里面都是存储的键值对,必须注意这个值必须是字符串,如果你存的是1、true,那么取出来就是“1”跟“true”,而且不能将undefined存入,如果要存储对象必须要用JSON.stringify()强转字符串存入。目前localStorage在各个浏览器存储容量各有不同,对于谷歌通常一个值最大是5M,其他浏览器得用插入字符串的方式,用trycache获取溢出异常来获取最大存储容量。

1、使用localStorage:

        上图的内容都是存储在localStorage里的内容。localStorage应用最多最广泛,几乎所有的网站在localStorage都会满满的存进一堆东西。这是因为几乎很多的网站都是允许用户不登录就可以浏览的,那么由于用户没有登陆,这些网站的后台就记录不了该用户的行为。记录用户行为的重任就落在localStorage身上了。localStorage的生命周期是理论永久,而且是不可能被爬虫爬到的,如果用户或者代码不去手动清理它,它会一直存在。至于存储什么内容,得看你想怎么做了,据我观察,各大网站一个共识就是存储用户信息、主题(通常是夜间日间模式)、搜索历史、上次播放的位置等等,我的音乐网站就存储了音乐播放时间(就是视频的从上次观看的时间播放)、次数、播放历史、是否开启引导等等(有趣的是,百度文档就把浏览限制存到这里面,如果你浏览百度文档被限制继续查看,请把localStrage内容清空刷新页面)。

localStorage的API非常简单,通常会用到localStorage.setItem("key","value")设置键值对,用到localStorage.getItem("key")来获取key对应的value字符串,如果不存在就会返回null而不是undefined,使用localStorage.removeItem("key")去移除key对应的localStorage,使用localStorage.clear()去清空localStorage。例如:

<script>console.log("取localStorage的值name:"+localStorage.getItem("name"));//第一次取肯定不存在,此时返回nulllocalStorage.setItem("name","hoppinzq.com");//存一个值console.log("取localStorage的值name:"+localStorage.getItem("name"));//存了才能取出来,返回hoppinzq.comlocalStorage.removeItem("name");//删除 或者localStorage.clear()console.log("取localStorage的值name:"+localStorage.getItem("name"));//已经被删了,返回nulllocalStorage.setItem("name","hoppinzq.com");//存一个值</script>

 是不是很简单呢?看看我的网站是怎么写的吧

if (typeof(Storage) !== "undefined") { //浏览器支持Storage或隐私模式吗?if (localStorage.getItem("historySongs")) {  //先看看历史歌单的键值是否已创建historySongs = JSON.parse(localStorage.getItem("historySongs")); } else { //历史歌单不存在时,一般用户第一次进到你页面(或者请过缓存)肯定不存在,所以需要加这个判断historySongs = [];}var isE = false;//这里是判断在听的歌曲是否包含在历史歌曲里,不用管for (let historySong of historySongs) {if (historySong.id === music_id) {isE = true;break;}}if (!isE) {historySongs.push(e.jPlayer.status.media);localStorage.setItem("historySongs", JSON.stringify(historySongs));//设置历史歌曲}
}

2、使用sessionStorage:

        上面的内容是我的网站的单页在sessionStorage存储的东西,有各种请求信息,图片等等。首先sessionStorage就像后台的session作用域,它的一次生命周期是一次临时会话,用户一旦刷新页面或者重新打开页面等执行路由刷新页面的操作都会导致sessionStorage内的内容清空 ,因此使用场景很受限,但是对于很多后台管理系统的前台来说简直是个宝。我观察了很多大型网站基本没有使用sessionStorage的,我主要是用来存储一些特定请求(比如分页,因为该请求不需要刷新页面)和媒体图片这样的资源,因为作为文件一般而言是最少被修改(忽略那些天天改头像的)。 

        如上面动图,在进入第二页的时候会请求拿到这些图片的路径,加载图片会花费大部分时间和占用很多带宽,而返回第一页的时候基本是秒加载,不使用任何流量,流畅到就好像做了个假分页。这样看来sessionStorage对于图片多的页面非常友好,因为图片大小很小,但对于大的媒体资源就好像无能为力了。

        sessionStorage的API很简单,跟localStroage一样。下面就看看我的网站是怎么写的吧。

/*** 缓存网易云图片* 注意:第一次加载的图片不建议直接使用缓存的图片,因为要花更多时间,第二次使用即可* @author zq* @param {Object} wyyUrl 图片url :格式 :* https://p3.music.126.net/t_Ax6p2zxfsBkymFbk6fMA==/109951165985230942.jpg*/
function cacheNetBaseCloudImg(wyyUrl) {//这里解析了一下,为了保证sessionStorage的键不重复而且通过图片id能拼接出来let cacheImageNameIndex_1 = wyyUrl.lastIndexOf("/") + 1;let cacheImageNameIndex_2 = wyyUrl.lastIndexOf(".");let cacheImageName = "image_" + wyyUrl.substring(cacheImageNameIndex_1, cacheImageNameIndex_2);var StorageCacheImageUrl;//先看看要加入缓存的图片是否已经在缓存里了StorageCacheImageUrl = sessionStorage.getItem(cacheImageName);if (StorageCacheImageUrl != null) {//如果缓存里有该图片,直接返回缓存的图片URL对象return StorageCacheImageUrl;} else {//使用原生ajax获取图片URL对象(或者base64)var xhr = new XMLHttpRequest();var blob;xhr.open('GET', wyyUrl, true);xhr.responseType = 'blob';xhr.onload = function() {var data = xhr.response;blob = new Blob([data]);// var reader = new window.FileReader();// reader.readAsDataURL(blob); // reader.onloadend = function() {// 	localStorage.setItem(cacheImageName, reader.result);//reader.result就是图片的Base64码// }var blobUrl = window.URL.createObjectURL(blob);//创建图片的URL对象cacheName.push(cacheImageName);sessionStorage.setItem(cacheImageName, blobUrl);return blobUrl;};xhr.send();}
}

观察上面代码注意以下几点: 

1、我是保存的图片URL对象,就如同java一样,当页面刷新关闭的时候会执行dom卸载操作跟垃圾回收,这个URL对象就被垃圾回收了失去价值了,所以sessionStorage存的东西实际没什么用处了,因此必须给页面绑定beforeunload事件手动清除sessionStorage内的图片URL,其实页面刷新的时候会自动清除,但是。。讲究!!!

$(window).on("scroll", function() {//绑定滚动事件。。。}).on("beforeunload", function() { //给页面刷新跟关闭绑定事件,手动清除缓存,并存储用户最后一次搜索的内容localStorage.setItem("lastSearch", $("#search").val());sessionStorage.clear();//清除sessionStorage内容,防止出问题
});

2、在上面代码cacheNetBaseCloudImg方法的注释里有一个获取图片base64码的代码,你可以存储base64码到localStorage里面,下次直接丢到img标签也可以。但是我不建议这么干,因为一来图片太多不好管理,得加入时间戳机制去隔一段时间清理一下(比如允许用户一个月只能修改一次头像),不然的话很可能就导致头像不同步了;二来万一图片很大就会溢出导致存不进去。那么如何存储大文本文件、媒体文件(音乐视频)呢?

其实现有的Storage也是能实现的,思路就用链表的思路,我们只需要先创建一个存储地址的字符串,然后把大文本文件转成base64码分几批存入localStorage,每一批存储一定量base64码和指向上一个localStorage地址的字符串,读取的时候拼起来就行。这个思路有很多,反正你能把分割的base64码正确还原即可。由于代码太多就不展示,有兴趣可以访问demo链接,或者粘贴下载文件 到浏览器上下载源代码。

3、使用Cookie

        其实cookie这个东西在前端讲有点尴尬,因为cookie一般都是后台创建的并赋予它的生命周期(前台也能创建,有一个很好用的谷歌的cookie.js,但是本人前端目前没用过,以后会补充)。它的生命周期并不像localStoage一样是永久的,也不像sessionStoage是一次临时会话,你可以存一些东西而且完全不必手动去清理它。前端去创建cookie并存储数据我暂时没用过,因为我对于捕获用户行为跟喜好并没有太多思路,我都是是在后台操作。就是帮你记住你的用户名。大多数是用户登录后台会创建cookie去存储用户登录的唯一标识token。

        通过ajax可以携带cookie的特点,后台通过校验cookie的内容来辨别请求是登录用户发起的还或是伪造的。

//ajax
xhrFields: {withCredentials: true //这个配置项就是请求携带cookie
}
//axios
withCredentials: true
//Fetch
fetch(url, { credentials: 'include' })

4、使用IndexedDB跟Web SQL

        首先这两个东西可能需要前端稍微了解一下数据库表和一点点sql的知识,Web SQL底层是SQLlite,但是除了谷歌都不支持。indexedDB跟sql语句没什么关系,而且比较难用,但它的value可以存储任何数据类型包括undefined,基本没有容量限制(这取决电脑跟手机),因此我们用这个存储媒体blob对象或者base64比较好。所以为了照顾前端,就找到了一个开源的js组件localforage.js(下载链接:下载文件 )(中文文档地址:localForage 中文文档)。该组件将IndexedDB封装的很简单,API也很贴心的仿照了Storage的API。

下面我们用localforage.js将音乐存入IndexedDB中,真正实现一次加载以后就可以不用流量播放。

首先分析:网易云音乐的url是http://music.163.com/song/media/outer/url?id=22766042.mp3,也就是?id=音乐id.mp3。

可以看到,该链接返回302响应码,并重定向到了真正的音乐的接口。我们首先要获取的是音乐文件的blob对象,所以我们得用ajax去请求这个接口, 但是很蛋疼的,ajax请求不支持重定向,因为通过ajax捕获重定向的url是有一定风险的,有可能你接收的是流,抑或是另外的重定向(淘宝跟拼多多就很喜欢套娃),responseType就很难声明了。所以我们要在后台通过HttpCilent模拟这个请求,关闭重定向,并捕获302重定向响应码,将真正的音乐url返回给前台:

/*** 获取重定向后的URL* @author:ZhangQi**/
@RestController
@RequestMapping("/music")
public class MusicController {@GetMapping("/getMusicRealUrl")public ApiResponse getMusicRealUrl(String url){HttpClient client = new DefaultHttpClient();HttpParams params = client.getParams();params.setParameter(ClientPNames.HANDLE_REDIRECTS, false);//禁用自动重定向HttpContext context = new BasicHttpContext();HttpGet get = new HttpGet(url);try {HttpResponse response = client.execute(get,context);HttpHost host = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);HttpUriRequest request =  (HttpUriRequest) context.getAttribute(ExecutionContext.HTTP_REQUEST);int code = response.getStatusLine().getStatusCode();if(code==302){HttpEntity entity = response.getEntity();if(null != entity){Header[] headers = response.getHeaders("Location");if(headers!=null && headers.length>0){String redirectUrl = headers[0].getValue();return ApiResponse.data(redirectUrl);}}}else{return ApiResponse.fail(-1,"不是重定向的页面");}} catch (ClientProtocolException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}return null;}
}

前台将音乐blob对象放入缓存代码:

        /*** 缓存网易云音乐* @author zq* @param {Object} musicURL* url格式:http://music.163.com/song/media/outer/url?id=22766042.mp3*/function cacheNetBaseCloudMusic(musicURL) {let cacheMusicNameIndex_1 = musicURL.lastIndexOf("=") + 1;let cacheMusicNameIndex_2 = musicURL.lastIndexOf(".");let cacheMusicName = "music_" + musicURL.substring(cacheMusicNameIndex_1, cacheMusicNameIndex_2);localforage.getItem(cacheMusicName, function(err, value) {if (value == null) {//获取真正的网易云播放url$.get("http://hoppinzq.com/music/getMusicRealUrl?url=" + musicURL,         function(data) {if (data.code == 200) {let realUrl = data.data;var xhr = new XMLHttpRequest();var blob;xhr.open('GET', realUrl, true);xhr.responseType = 'blob';xhr.onload = function() {var data = xhr.response;blob = new Blob([data]);localforage.setItem(cacheMusicName, blob);};xhr.send();}})} else {return value;}});}

从代码看出,localforage的API跟Storage如出一辙(中文文档地址localForage 中文文档)也是getItem,setItem(),removeItem(),clear()。但是贴心的设计了回调函数如localforage.getItem("key",function(err, value){},

最后,将缓存里的blob对象解析成URL对象放到音乐播放列表就行了,大家可以在demo里断网体验一下哦:

        /*** 动态设置播放列表* 注意:在执行setPlaylist方法会执行init方法,该方法会把当前播放时间currentTime置为0,这会导致音乐重新从列表第一首开始重新播放* 而原来你在听的歌曲会停掉,我的做法是:在播放时就建立一个被静音的播放源,在执行此法时将静音去掉,此时声音会增大,这是因为原有* 的播放源的音量使用的系统音量乘以用户拖动的音量百分比,在切换播放源时会立即使用系统音量因此要手动×上一个比值。* @param {Object} songList*/function setMusicList(songList) {timeout(1).then(function() {//遍历歌曲列表,将存在缓存的歌曲的URL对象替换网易云URL$.each(songList, function(index, value) {localforage.getItem("music_" + value.id, function(err, value_) {if (value_ != null) {var url = window.URL.createObjectURL(value_);value.mp3 = url;}});})//微微的缓冲return timeout(1000);}).then(function() {myAlert("切换声音源中。。。");myPlaylist.setPlaylist(songList);audio_hidden.muted = false;var wi = parseFloat($(".jp-volume-bar-value").width() / $(".jp-volume-bar").width()).toFixed(2);audio_hidden.volume = wi;});}

        这个地方作为一个纯前端是不太好理解的,因为现在直接访问文件资源的接口是一定要做重定向并且携带cookie、时间戳、加密的(网易云音乐真实URL是根据时间戳自动生成的,写错一个字母,你都会吃到网易云4xx黑名单套餐)。现在很少网站能做到让你免登录下载的(本站就是免登录>-<),因为这些接口会直接返回流,此类接口一旦被盗刷,频繁的IO操作会严重降低服务器性能并造成带宽堵塞。最后可能导致其他用户根本进不到我的网站,甚至可能导致服务器宕机。因此,必须要做一个重定向,并在重定向前对接口进行有效的管控。

具有很多手动缓存页面的源代码:链接

在线访问:链接


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

相关文章

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

前言 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方法对路径…

Unity Shader入门精要之Unity 提供的内置文件和变量

Unity系列文章目录 文章目录 Unity系列文章目录前言5.3.1 内置的包含文件5.3.2 内置的变量 二、Unity 提供的Cg/HLSL 语义5.5 程序员的烦恼&#xff1a;Debug5.6 小心&#xff1a;渲染平台的差异5.7 Shader 整洁之道参考 前言 上一节讲述了如何在Unity 中编写一个基本的顶点/片…

《python数学实验与建模》(10)图论模型

10.1 写出图10.20所示非赋权无向图的关联矩阵和邻接矩阵 绘制图 import networkx as nx import pylab as plt import numpy as np Anp.zeros((6,6)) List[(1,2),(1,4),(2,3),(3,4),(3,5),(3,6),(4,5),(4,6),(5,6)] for i in List:A[i[0]-1,i[1]-1]1 Gnx.Graph(A) posnx.spring…

2021年美国数学建模C题的数据处理

2021年美国数学建模C题的数据处理 C题数据分类存放部分批量提取图像数据转化jpg图像格式 C题数据分类存放部分 在拿到C题的数据后&#xff0c;避让要做的一个事情是图像数据的分类。根据2021MCM_ProblemC_ Images_by_GlobalID表格中&#xff0c;可以将图片和ID号对应起来&…

Unity Shader入门精要笔记(五):其他数学相关介绍

本系列文章由Aimar_Johnny编写&#xff0c;欢迎转载&#xff0c;转载请标明出处&#xff0c;谢谢。 http://blog.csdn.net/lzhq1982/article/details/73747162 前两篇介绍了Unity Shader的主要数学部分&#xff0c;书上还有些相关的数学介绍&#xff0c;将在这篇做最后的总结。…

2020年数维杯国际大学生数学建模B题股票价格的混沌模型求解全过程文档及程序

2020年数维杯国际大学生数学建模 B题 股票价格的混沌模型 原题再现&#xff1a; 上市公司股价的变化可以直接反映上市公司的经营状况和市场的认可度。股票价格的建模和预测一直是一个难题。最重要的因素是股票价格既有趋势因素又有随机因素。因此&#xff0c;股票市场是一个非…

ugpost_tcl文件

########################## TCL Event Handlers ########################## b.tcl - 3_axis_mill 这是 3 轴铣床。 Created by dp Wednesday, November 06, 2019 8:52:33 AM China Standard Time with Post Builder version 10.0.3. #####################################…