前言
上一篇⬆️写了关于前端如何缓存 ajax 加载的数据,如果对这个话题感兴趣又没有看过的童鞋,不妨前去一观:https://www.jianshu.com/p/138d91128147。
这一篇文章是上一篇文章的后续,所以,如果没看过上一篇文章的话,有些内容可能理解起来,还是有点障碍的,因为有些前置的内容,这篇文章不会细讲,而是会直奔主题。
很多时候,浏览器会强制对页面里面的静态资源进行缓存,以提升页面的二次加载速度。
当然这种说法,其实是不太准确的,因为我们是可以通过多种方式来控制这种“浏览器默认的缓存机制”。
既可以通过在 html 的 head 中加标签来控制,也可以通过在后端响应前端的请求的响应头中设置参数来控制。
如果想详细研究的话,请参考 stackoverflow 上的相关讨论,已经很详细了:https://stackoverflow.com/questions/49547/how-do-we-control-web-page-caching-across-all-browsers。
这种浏览器默认的缓存的机制,其实是不可靠的,因为对于我们来说,不是完全可控的。
如果我们想要一种完全可控的前端图片缓存机制,我们该如何做呢?
别着急,耐心往下看,你就明白应该怎么处理了。
前端默认加载图片的几种方式
我们知道的是,默认情况下,我们加载图片,都是借助 html 标签来加载的,我们一般的做法有几种:
1. html 方式
直接写在 html 代码中,将 img 标签的 src 设置为我们图片的路径。
2. JavaScript 方式
通过创建一个 Image 对象的实例,然后将该实例的 src 属性设置为我们图片的路径。
var img = new Image();
img.src = 'https://www.baidu.com/img/bd_logo1.png';
img.onload = () => {
console.log('图片加载完成✅!');
}
上面 这两种方式,都不是我们可控的。
因为这两种方式,有个共同点,都不是通过 ajax 方式加载的,而是浏览器引擎解析了以后,自动请求的。
打开 devtools,你就会明白这种说法是什么意思。
但是如果,我们将图片的加载完全改成 ajax 加载,那么我们就可以完全控制,是否需要对图片做缓存了。
但是如何做呢?
缓存用我们上一篇文章的中的 IndexedDB 就可以实现,但是我们如何 ️ 通过 ajax 的方式,来加载图片呢?
如果你没尝试过,那么接下来,我会提供几种可行的方案。
当然,fetch 会遇到
ajax 加载图片
这里我们借助 fetch 这个 api,来方便我们发送 ajax 请求。
fetch(imgUrl);
然后我们可以通过 then,拿到请求到的对象,但是这个对象需要封装成文件,我们才能使用。
fetch(imgUrl)
.then(response => response.blob());
1. URL.createObjectURL
然后再通过 then 的链式调用,拿到封装后的文件对象。
在这种方式,我们通过 URL.createObjectURL 这个 api,对拿到的文件对象进行封装,封装了以后,会返回一个新的 url 对象,这个对象可以理解为我们浏览器中的该对象的引用或者指针,就是指代该对象的。
如果不了解 URL.createObjectURL,可以参考下该页面:https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL。
fetch(imgUrl)
.then(response => response.blob())
.then(blob => {
var imgUrl = URL.createObjectURL(blob);
callback(imgUrl);
});
如果不理解为什么要这么用,可以参考下面这个在 input 选择图片上传的过程中预览图片的小 demo:https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications#Example_Using_object_URLs_to_display_images。
2. base64 格式
base64 也是一种常见用来存储图片的格式,我们可以借助其将图片编码为字符串,便于我们存储在数据库或者代码之中。
我们当然也可以借助 base64 的方式,来存储我们通过 ajax 方式请求到的图片。
我们直接将获取到的 blob 文件,通过 FileReader 对象上的 readAsDataURL 这个 api,将其读取成 base64 格式的图片。
fetch(imgUrl)
.then(response => response.blob())
.then(blob => {
const fileReader = new FileReader();
fileReader.onload = (e) => {
var base64 = e.target.result;
console.log(base64);
};
// readAsDataURL
fileReader.readAsDataURL(blob);
fileReader.onerror = () => {
console.error(new Error('blobToBase64 error'));
};
});
理清楚了这个思路以后,接下来,就可以接着上一篇文章的思路来了。
我们直接将 blob 存储在 IndexedDB 数据库中,我们借助 read 从 imagePool 这个表中读取我们请求的 url,当读取成功了,就证明这个文件我们曾经读取过,属于二次加载。
那么我们 then 的 resolve 方法中就会被调用,否则 reject 方法就会被调用。
const resolve = (data) => {
callback(URL.createObjectURL(data));
}
const reject = () => fetch(url)
.then(response => response.blob())
.then(blob => {
add(blob, url, 'imagePool');
var imgUrl = URL.createObjectURL(blob);
callback(imgUrl);
});
function getImageByXHR(url, callback) {
read(url, 'imagePool')
.then(
reslove,
reject
)
.catch(function(err) {
console.log(err);
});
}
当然,就像我们之前讲过的那样,凡事都有利弊,需要合理的衡量利弊。
在性能和内存占用以及兼容性之间,找到一个合适的平衡点,足以。