前端如何缓存blob文件之图片
曾经在网上看过:web开发其实就是json编解码。仔细一想,这句话真是精辟。。。不过再往深处想,json编解码只不过覆盖了web开发的大部分(大部分时间都是在处理字符),其实还有一部分是处理二进制数据的(比如音视频在线应用)。不过,现在前端也有出现了File,Blob,TypedArray等API,使得前端处理二进制数据特别方便。
这里说下我们的方案,鉴于安全方面的考虑,我们小组在处理图片的时候不允许前端去直接拼接图片路径去访问,应该通过后端接口去访问(针对登录用户,header中需要带token字段),导致我们前端访问图片的时候需要像普通接口(比如登录,修改用户手机号)一样请求回来然后再处理。
我们使用Axios库来进行ajax请求,前端需要在header中指明 responseType: 'blob'
,然后我们前端得到的就是blob类型的图片文件。
接着我们需要在前端展示这个blob类型的图片,这个嘛,so easy!URL.createObjectURL()
api可以轻松搞定(使用对象URL显示图片)。
我封装了一个函数,效果搞定了:
一会,测试过来跟我说,上边两个页面切换的时候,加载时间过长(每次切换都请求图片数据)。呀,这也简单啊 ,我把 对象URL
加个缓存就就行了。
objectURL = URL.createObjectURL(object);
的返回值 是DOMString (一个UTF-16字符串),我们只要把 返回值存在localStorage ,sessionStorage,cookie,indexedDB,webSQL中任意一个即可,浏览器本地存储字符串太在行了。没有什么是缓存不能解决的,不过不能,就。。。
不一会,测试又跑过来了:退出公众号,在进入到有图片的页面发现,图片404了。。。哎呀,这是什么情况?
MDN解释如下:
内存管理
在每次调用 createObjectURL() 方法时,都会创建一个新的 URL 对象,即使你已经用相同的对象作为参数创建过。当不再需要这些 URL 对象时,每个对象必须通过调用 URL.revokeObjectURL() 方法来释放。
浏览器在 document 卸载的时候,会自动释放它们,但是为了获得最佳性能和内存使用状况,你应该在安全的时机主动释放掉它们。
当用户在刷新页面的时候,会先执行 document 卸载操作,这些 对象URL 也被垃圾回收掉了,当用户再次进入的时候,访问的还是缓存的资源,然而这个资源的实际内存已经不存在了,所以会出现图片访问404的问题。好比那句话,人活着钱没了的感觉。。。
这个解决起来也简单啊 ,我们在网页卸载的时候主动清空这些缓存不就行了,最后用户再次进入页面的时候重新再去请求一次。想法有了,说干就干
浏览器有个 Window: beforeunload event
api:
当浏览器窗口关闭或者刷新时,会触发beforeunload事件。当前页面不会直接关闭,可以点击确定按钮关闭或刷新,也可以取消关闭或刷新。
window.addEventListener('beforeunload', (event) => {
// 执行我们清空图片缓存的操作
StorageUtil.clear()
// Cancel the event as stated by the standard.
event.preventDefault();
// Chrome requires returnValue to be set.
event.returnValue = '';
});
打完收工。。。
结果上真机上,发现微信浏览器根本不支持这个。。。后背一凉,不知道该咋办了。算了,估计这条路走不通了,我就问了移动端的小伙伴咋解决这个问题的,他有可能会遇到跟web一样的问题。移动端小伙伴:好像框架自动做了缓存(他们使用flutter),Image使用过一次会自动缓存,但是当app再次启动的时候缓存会消失,需要重新请求一次。这不跟web上差不多吗,只不过web上没办法判断用户何时进入页面何时离开页面罢了。
这问题了其实折腾了我一下午了,一阵尿意袭来,算求,上个厕所先。。。在嘘嘘的时候,我就在想:js如果能操作本地文件就好了,我可以把blob文件复制一份到客户端某一目录下,这样实现缓存不就搞定了吗!问题来了:前端如何把blob转为文件存下来啊?
其实js是可以读取客户端文件的,比如我们在使用input上传文件时,哎,有了。
FileReader
api不就是读取文件的吗:
FileReader 对象允许Web应用程序异步读取存储在用户计算机上的文件(或原始数据缓冲区)的内容,使用 File 或 Blob 对象指定要读取的文件或数据。
我们可以把 blob 文件转为 base64字符串啊:前端处理字符串太在行了。。。
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
console.log(reader.result);
}
最后,我将 blob 转为 base64 缓存在localStorage。终于,功能完成了。
后续优化
我们知道转base64会导致内存占用变大,本来3个字节结果变为了4个字节,我在开发时发现:我们的图片转为 base64 后大约在 30kb 左右(前提时对用户上传的图片进行压缩),有可能导致localStorage被占满,我们需要提供删除图片缓存的机制:
- 在用户退出登录的时候清空登录数据和图片缓存数据
- 检测本地缓存占用大小,提供用户一个类似于app上清空缓存的操作,主动提示用户去清空图片缓存
- 下下策,互相伤害,将别的域名下的localStorage删掉,只保留本域名下的缓存(这招可太阴了)