上文中我们提到了浏览器在初次拿到资源后,会将其缓存起来,以供后续重复使用提高效率。你以为这样就完了吗?并没有。精打细算的程序猿们在想:除了浏览器自动去缓存资源外,我们能不能提前加载那些后续可能会用到的资源呢?这样我们后续使用时便可直接从内存中取。所以预加载就这样诞生了。下文我们将以图片资源为例子,一步步展开如何实现预加载。后文则会着重分析预加载与浏览器自动缓存策略的区别。废话不多说直接开始吧!
首先我们在工程路径下放几张命名规律的图片,分别以G+索引命名,这样我们可以让他们自动循环加载
然后我们要new一个Image对象,并通过将图片资源地址赋值给对象的src属性,来将图片资源保存进缓存。通过Image对象引入缓存中:
var imageArr = []; var index = 1; var img = new Image(); //每次图片加载时,都会触发loadHandler img.addEventListener("load", loadHandler); //为了不影响主页面原本的加载运行,我们通过onload事件在主页面初始化渲染完毕后触发,在用户无感知的情况下,偷偷去获取资源 window.onload=function(){ //通过给src赋值图片地址去请求资源,同时这行代码会触发加载事件 img.src = "./image/" + "G" + index + ".jpg" } function loadHandler(e){ //在加载下一张图片前,先将上一张图片保存进数组里 //img对象调用的addEventListener,所以这里的this就是img //函数是哪个对象调用的,this就是这个对象 imageArr.push(this); //拿到对象资源后要把load监听事件移除掉,否则会一直占据内存 this.removeEventListener("load", loadHandler) //下面开始下一张图片的加载 index++; if(index > 4){ console.log(imageArr); return ; //递归在这里终结 } var imgNext = new Image(); imgNext.addEventListener("load", loadHandler); //每加载一次就会递归一次loadHandler方法 imgNext.src = "./image/" + "G" + index + ".jpg"; }
运行效果:
但这种初级写法有个坏处,我每拿一组不一样的图片资源就得修改代码,所以我们改进一下:将业务逻辑抽离出去,只留下公共部分打造成工具方法。每次预加载时,只需要调用公共方法,传入我们准备好的资源路径,和具体要做的业务逻辑,剩下的事情就交给工具方法去做吧!
var imageArr = []; //先将所有要加载的图片路径放入imageArr中 for(var i = 1;i <= 4;i++){ imageArr.push("./src/image/" + "G" + index + ".jpg") } myfunction(imgList){ console.log(imgList); } //偷偷预加载资源 window.onload=function(){ getImage(imageArr, myFunction) //调用工具方法一步到位 } //工具方法如下 function getImage(imageArr, callBackFunc){ var image = new Image(); img.srcList = imageArr; //放置所有图片的路径 img.callback = callBackFunc; //放置业务处理方法 img.imgList = []; //放置所有图片资源 img.index = 0; //记录第几张图片 img.addEventListener("load", loadHandler); //添加加载事件 img.src = img.srcList[img.index] //触发加载事件 } function loadHandler(e){ //通过cloneNode函数先把this,也就是上一张图片放入数组中 this.imgList.push(this.cloneNode(false)); //开始加载下一张图片 this.index++; if(this.index >= this.srcList.length){ this.removeEventListener("load", loadHandler) //移除监听事件 this.callBackFunc(this.imgList); //拿到全部资源,中断递归,处理业务 return ; } this.src = this.srcList[img.index]; //这里会触发新一轮的loadHandler }
from disk cache和from memory cache
我们知道预加载是将资源保存在内存中的,但是这和浏览器的自动缓存有什么区别呢。下面我们先来看看F5刷新页面,同一缓存资源是如何获取的
我们看到F5刷新后loading.gif这个资源还是从内存中获取。这是因为刷新并不会终结当前页面原有的线程,所以它还是能从线程中获取资源。但是当我们重新打开一个新页面呢?
我们可以看到该资源变成了从磁盘中获取,这里就从侧面印证了浏览器缓存进内存策略其实和我们手动预加载进内存是一样的,都是放在当前页面进程(当然也有放在浏览器独立进程ServiceWorker中)。而从当前页面线程的 memory cache 中获取缓存内容时,浏览器会忽视例如 max-age=0
, no-cache
(不包括no-store)等头部配置,直接获取资源。
关于from memory cache、from disk cache、预加载,这三种方式是如何受上文提到的响应头 Cache-control 的影响,大家可以看这位百度前端大佬的文章,文末有详细的比较过程,这里就不赘述了,请戳https://github.com/easonyq/easonyq.github.io/blob/master/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95/others/cache.md