JS本地缓存方案
by addy 原创文章,欢迎转载,但希望全文转载,注明本文地址。
Seajs-localstorage作为一个seajs的插件,本质上是为了模拟Html5自身的application cache,因为application cache有其先天不足,所以才有了这个插件。出于对优化前端的性能考虑,就打算用JS本地缓存试试水,本文且就其源码做一些分析以及其实践经验总结。
首先来看下程序的逻辑图,代码不多,逻辑也比较简单。看源码请猛戳seajs-localStorage.
manifest
利用localStorage,模拟manifest的功能,比H5提供manifest要好用,manifest.js文件作为版本控制文件,记录文件的版本号,实质是一个文件键值对,类似这样的:
{ 'module/a.js':1, 'module/b.js':2, 'module/c.js':3 }
插件的工作机制
- 进入应用,先加载manifest文件,如果本地存储中不存在该文件,则认为首次进入站点,并将该文件存入manifest中
- 页面再次刷新会重新请求manifest文件
- 首先会判断version是否修改,不修改程序就会认为此次访问不需要任何更新,所有文件全部从本地存储中读取;
- 如果version前后发生了改变,资源文件变化,于是会通过比对存在本地的manifest文件和请求而来的manifest循环递归出哪些资源文件需求修正更新,并在接下来的fetch阶段直接从网络下载
拉取到的文件eval执行,并且将拉取的新文件写入localStorage,命中缓存则从localStorage取出来执行。类似单点更新,最明显的就是较少了HTTP请求,请求的字节数也将减少。而且消灭304请求。
业务场景
- 多页面,各页面间有共用的JS模块
- 模块多,将近200个
需要维护一个manifest文件,尤其是多人开发时,即使自动化维护版本,200多个文件是个灾难。
最终部署的时候一个文件对应多个模块;按模块的更新频率划分。
- 减少服务器combo合并的文件数,一定程度上提高性能。
- manifest文件更容易维护。
代码分析
下面看下源码中的几个比较重要的函数:
- Storage对象提供了针对localstorage的增删改查功能。
- fetchAjax,所有的js文件都是通过fetchAjax方法去拉取,方式为异步拉取,如果脚本是跨域的,这点尤其要注意,提前做好跨域请求的工作。拉取失败将走正常的加载方式。
- splitCombo,正式拉取文件时,请求的是combo文件,但由于模块比较多,在打包的时候提前合并了一次。splitCombo方法就是用来拆分请求返回的代码文本,与manifest中的文件名一一对应,这样才能对应存储。
- 插件的核心就是重写 seajs的module.prototype.fetch方法,在获取模块的时候做存储逻辑。
- validate方法将判断请求的文件是否有效。其实用过seajs开发的方式同学应该知道,模块id与url是一样对应的,模块id名与文件名是一样的。因此验证的第一步便是url是否与代码中的module id一致。再就是判断两个文件的版本号是否相等。
- use 就是执行请求返回后的代码或者本地缓存的代码,若是css直接插入一个节点
// Css文件: node.styleSheet.cssText = code; // js文件: if (window.execScript) window.execScript.call(window, _code); else window["eval"].call(window, _code)
所有的异常将导致走正常加载模块的方式。
缓存命中统计
对本次seajs-localtaorage的实践应用,统计了本地缓存的命中概率,随机抽取一天的数据:
由于本次只在两个页面上使用这种方式,数据不是很多,一共上报20w左右,命中率有50%+,还算是比较好的情况,后期将全量到站点个页面。
后期计划
结合文件增量更新的方式,进一步减小更新文件的大小。
本文为原创文章,可能会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,谢谢合作
个人知乎,欢迎关注:https://www.zhihu.com/people/iamaddy
欢迎关注公众号【入门游戏开发】
近期评论