要打开一个页面,总会牵扯到很多资源,其中包括但不局限于以下这些内容:
页面本身,页面包含的自页面(比如通过frame,iframe,等等);
CSS与JS等脚本(如果你安装了Mathematica,那么你还可以调用这货的网页脚本来执行各种命令。此外还有古老的VBS甚至WSHost的Shell);
各种图片等资源文件;
其它(这句好废哦……)
因此,打开一个页面,不单单是打开一个页面,而是需要再如很多东西。
下面就来说以下关于页面缓存的事。
页面缓存是干嘛的?
说到这个,就要先说一个浏览器的基本机制。
以Webkit为例(我也只熟Webkit。。。)
Webkit有一个Resource模块,用来管理各种资源的加载与使用——如果你有幸看过图形图像引擎gdx的话,你会发现它也有一个Resource模块,干的是相同的事情,不过针对的是OpenGL/OpenGLES罢了(所以还会缓存shader文件)。
这个模块的基本逻辑是这样的:
你问我要一个资源,如果没有,就按照URL去请求;如果有,就询问是否过期(内存中是否过期以及资源本身是否过期),如果过期,就按照URL请求,如果没过期,就直接返回这个对象。
因为有这个模块,所以如果你在一个网页里同时需要现实多张一样的图片,它们不会被从服务器请求多次,而是只请求一次,然后所有需要显示图片的Webview组件同时加载这同一份资源。
所以,在网速较慢的时候,你会发现这多张图片是被同时从上到下刷新的;如果加载的是GIF,那么这些GIF也是按照同一个起点在播放的(这里牵扯到了一些和EventLoop以及GIF的GetFrame相关的机制,所以略有不同,这里不说了)。
这里重要的就是:浏览器对资源是有做缓存的,只不过会检测资源是否过期,仅此而已。
严格说来,上面的说法还不完全对。
比如,如果是内存中资源过期,那么浏览器并不是直接从URL请求,而是会先从本地缓存读取,同时判断本地缓存是否过期——这就是所谓的资源本身是否过期。
也就是说,如果一份资源在本地缓存中存在,且没有过期,那么是不会从URL指定位置重新要数据的。
这就是资源的Head中的Expire的作用——它告诉浏览器这份资源多久过期。当然,也可以通过发别的Head让资源立刻过期。
老式互联网时代的老式网站里经常会看到很多资源的Expire很长很长,于是你会郁闷地发现访问一个网站结果看到的还是老的图片,这个很郁闷。老式网站的一般解决方案是:把资源换个名字。。。。。。浏览器里会保留老的资源直到过期,同时还有一份新的资源,所以IE的本地缓存往往十分巨大,Windows优化大师的一般瘦身攻略就是干掉IE缓存。。。
到这里,我们已经接触到了第一类页面缓存机制:Cache-Expire机制。
现代浏览器当然不仅仅有这种古老的机制,还有很多别的方案。
比如,HTML5中可以用来缓存的方案就很多,比如:
Application Cache,LocalStorage,FileSystem,WebDB,IndexDB,等等。
Application Cache是一个很牛叉的东西,它通过一份配置文档告诉浏览器哪些资源是直接保存在本地的,这个意思就是说:这份资源就在你的电脑上,访问的时候也是直接从你电脑上访问,而不需要在问浏览器。
和传统的Cache-Expire机制的不同就在于检测是否过期这件事上。
如果资源的过期时间很长,那么一旦网站有更新,你就无法即使更新图片,会有问题。而且这种机制对页面无效。另一方面,如果Expire很短,那么缓存十张图片,那么在你再次打开页面的时候,会发送十次资源请求,服务器回答没有更新可以拿老的资源(304信号),然后你才可以继续拿老的最远而不是请求新的资源,换言之存在一个询问是否过期的过程,十张图片就是十次,不是很节约。尤其网络环境不好的时候,就会导致加载速度变慢。
Application Cache就不同了,一个页面的所有资源是否过期完全由配置文档是否过期来决定,如果配置文档没有过期,那么整个页面的资源都不需要重拿;如果配置文档过期,那么再按照正常的Expire-Cache流程走——换言之,在不需要更新的时候,Application Cache只需要确认一次过期,而传统的是有多少资源确认多少自,浪费Http链接;而在缓存已经过期的时候,则和传统方案一致。
更重要的是,Application Cache由于上述机制真正做到了离线浏览——虽然传统的老的浏览器早就只是了离线模式,但还是需要用户自己来开启,Application Cache则是全自动的。
但Application Cache有一个问题,拿就是它是以文件为单位的,而且是由服务器决定的。如果是一些运行到一般的中间变量,那AC就无效了。
这个时候你需要LocalStorage。
LocalStorage将数据以Key-Value这种橙须猿很熟悉的形式保存在浏览器的本地缓存中,从而不会因为网页的关闭而消失。
而且,它不是以文件为单位,而是以变量为单位,这就很灵活了。
当我们作一个大型网站,或者一个WebApp(比如游戏)的时候,就会大量使用LocalStorage来保存一些用户设定或者游戏进度等信息。
基本上,如果一份信息不需要和别人交互,服务器也并不真的非要不可,那么就可以保存在本地——这就是LocalStorage的功能。
比如说,简书的黑夜/白天设置,这个其实完全可以保存在LocalStorage中——但如果用户都要求说我从不同的设备登录这个网站都要拿到相同的设定,拿就还是需要保存在服务器的(当然,就个人来看,这种要求可以忽略……)。
使用LocalStorage可以将这些无关的设定保存在客户端,从而节约服务器端数据库空间(虽然估计很有限),需要的时候从客户端拿嘛。
LocalStorage的胞弟SessionStorage也可以用来保存数据,不过这种数据一般就是单窗口有效的。SessionStorage的数据只在打开页面的窗体内始终保存,但和页面级的保存不同,你跳转页面后这些数据还在,直到你关闭这个窗口位置。
LocalStorage还提供update事件,从而可以做到一个网站的多个窗口之间的数据同步和通讯,这个对IM或者游戏来说很重要。
LocalStorage的一个限制是,不同的浏览器有不同的空间上限,比如Chrome是5MB,一般来说也是够用了。
比如LocalStorage更专业的就是WebDB了,以数据库的格式在本地维护数据,做游戏必备,这里不多解释。
Local Storage用来保存零散的变量,Application Cache则只能以配置好的文件为单位缓存文件,不够灵活。
这种的方案,就是超级重量级的大杀器:FileSystem。
FileSystem让网站设计者有权限在浏览器于用户本地的可用空间范围任意读写文件。
这就非常灵活了,不受Application Cache的配置限制,也不像Local Storage有格式和容量限制。
这货要怎么用,就完全看网站设计者高兴了。
下面来说一下比较常见的综合型缓存机制。
首先,传统的Expire-Cache机制肯定还在。
然后,就是网站的最稳定的部分,比如HTML框架,核心JS与CSS,这些都用ApplicationCache做缓存,基本都是半年一年甚至再也不会更改的,很稳定。
社交类网站除了上述框架外,最经常变动的,就是文章帖子内容,以及,各种图片声音等资源。
上述资源又可以分为几类——
有些是“一次性消费”,比如今天看了一篇文章,上面出现了某个用户的头像,但以后我再也不看了。
所有访问次数很少的(基本三次一下就可以认为很少),都属于一次性消费,这类东西没必要做缓存,不值当。
有些是“长期消费”,比如我关注的用户中最活跃的几个,他们的文章我常看,于是它们的头像对我来说就是长期消费——不但是头像这个图片资源,也包括它们的个人数据中稳定不变的部分,比如个人简介。这种资源一个两个人也就算了,人数一多,也就很可观了。还比如某些类型的网站会出现一些文章一个用户会长期经常反复访问(比如李毅吧置顶帖)。
还有一些是“稳定消费”,比如一个论坛的主题(包括一组CSS、JS和图片),基本我选定以后一长段时间里不会变。它们尴尬的地方是——相比框架性资源,它们没那么恒常;相对长期消费资源,它们又足够稳定。所以这类东西的缓存就要使用不同的方案。
对于稳定消费资源,就可以使用FileSystem写到本地,当用户换主题的时候就删除久主题,重写新主题。一套主题资源写到FS里,以后的开销都问FS拿,不需要再问服务器要,节约了流量和加载时间。
而长期消费中非图片声音等大型资源外,内容数据就可以用LocalStorage来做缓存处理——就算只有5MB对纯文本来说也足够了,当然你别跟我提什么你叔叔纯TXT的黄书有10GB这种奇葩案例……长期消费中的大型资源可以考虑用FileSystem做缓存。
当然,对于JS文件和CSS文件,可以使用FS缓存,也可以写入LS中(使用的是偶从LS中读取然后写入Script或者Style标签动态加载到页面中即可)。
用户个性化设置也可以用LocalStorage,最多就是每次有修改了告诉服务器,第一次浏览的时候拿一遍,而不需要每次都从服务器端拿一遍。
一次性消费,无视掉吧。
因此,我们完全可以创建一个Resource的JS模块来完成这件事。
它的作用就是根据请求资源ID来查看LS或者FS,如果有就返回本地缓存数据(如果是图片的URL就返回FS的URL),如果没有再问服务器要。然后可以开一个服务器接口是询问最新资源的TimeStamp,供浏览器去确认。
Resource模块的另一个作用,就是根据对资源的访问频次来决定什么资源使用什么类型的缓存,这是一个纯配置和计数,就不说了。
当然,上面是基本思路,实际构建模块的时候肯定还会有别的问题,这里就姑且忽略掉吧~~~(这句话好不负责任啊…………)