`Local Storage`缓存

2016/3/29 posted in  others

因为WAP触屏版的面向对象都是使用智能手机浏览器访问的用户,而基本上所有智能手机的浏览器都支持Local Storage,这样就可以使用Local Storage缓存一些东西。

之前都是使用Local Storage缓存一些用户信息,当做cookies使用,甚至还有一段时间Local Storage里的内容不能通过浏览器‘清楚访问痕迹’的功能删除,就使用Local Storage作为存储用户唯一标识的地方。

现在为WAP触屏版的优化思路就是将jscss文件存到Local Storage中,用户之后访问需要jscss的时候都从Local Storage中取,而不从网络中拉了。

美团的WAP触屏版是一个做的比较极端的例子

第一阶段

其实对于规模不是很大的网站,上线的流程不会特别严格,特别规范,那么对cssjs经常的修修补补是不可避免的。如果使用Local Storage缓存所有的cssjs,那么就还要制定一套Local Storage缓存失效的规则。所以我们暂时先只对用到的第三方jscssjqueryFramework7等)进行缓存

参考了《Web移动端使用localStorage缓存Js和css文件》1,这套代码基本实现了使用Local Storage缓存jscss文件的基本需求,对于简单的网站页面效果也是明显的。

但是有两个问题:

1、不能跨域。程序是用ajax请求cssjs的,由于ajax不能跨域,所以必须保证静态资源文件和网站要在同一个域名下,这个跟动静分离的思想有所违背。比较糙的解决方法就是将要缓存的文件映射一份在网站同域名下。

2、文件不能顺序加载。程序在拿到cssjs文件后,将这些文件的内容head.appendChild(js/css)html中,这样会让这些文件同步加载到html中,而缓存的文件加载的顺序不能保证一定在非缓存文件之前,如果之后的js的文件依赖于缓存的js先加载,那么就会报错。

举个例子:

<head>
    <script>
        whir.res.loadJs("jquery", __BASE_SERVER__ + "/js/jquery-1.8.3.min.js");
    </script>
    <script type="text/javascript" src="${__static_server__}/js/needsJQUERY.js"></script>
</head>

第二个文件需要jquery先加载,然后使用jquery的函数,但是jquery被缓存,且不能保证会在第二个文件处理前加载好。这样就会报错。

程序对顺序加载的解决方案是使用回调函数,保证数序:

whir.res.loadJs("jquery", __BASE_SERVER__ + "/js/jquery-1.8.3.min.js"), function(){
    whir.res.loadJs("needsJQUERY", "/needsJQUERY.js");  
});

这种解决的方法对于结构简单的页面是有效的,但是我们的页面都是依赖一个公用的宏,在公用的宏中申明了所有地方放的jscss,每个页面自己又有一套本页面有效的jscss,对于这种callback随页面变化的情况,前面说的解决方法不太适用。

比较糙的解决方法就是在加载最后一个缓存js文件的时候,callback方法中写location.reload();,页面强行刷新下

第二阶段

第二个问题相对来说更棘手,所以我们先要解决这个问题。

我们的思路是这样的:

  • 对于第一次来网站的用户,Local Storage为空,我们先使用正常的jscss引用的方法,然后再将jscss放入Local Storage中,但这一次不用Local Storage中缓存的文件
  • 对于之前来过网站的用户,Local Storage是有缓存的jscss,所以直接使用这些缓存的文件

判别用户之前有没有来过网站,我们就拿一个cookie来记录,如果用户有这个cookie值,则判断用户来过;如果没有这个值,则判断用户没有来过。

再者,如果用户的这个cookie值和我们pageVersion不一样,说明我们要缓存的js或者css版本号改变了,这时候,我们会先让cookie为空,将用户理解为一个全新的、第一次来的用户,然后继续整个流程。

这样好像就解决了用户第一次来,还要强制刷新页面的问题。直到我们用safari或者uc浏览器无痕浏览的功能访问网站的时候

safari的无痕浏览模式不支持Local Storage,并且会在localstorage.setItem的时候报错,自动停止程序,导致咱们缓存jscss时,用户访问的cookie写成功了,但是缓存没写进去。

uc浏览器的无痕浏览模式也不支持Local Storage,并且在将cookie值更改以后,新旧两个cookie都会同时存在,这样我们的程序就变成了死循环,检测到用户访问过,以为有缓存,实际没有。

所以我们对程序又做了修正:

  • 将添加cookie操作加入到loadJs的回调函数中
  • 在保证localstorage.setItem成功运行之后,我们才会把cookie值记录进去
<script type="text/javascript" src="${__static_server__}/js/localstorage.js"></script>
<#if local_storage_version != ''>
    <script>
        var lsv = "";
        var name = "lsv=";
        var ca = document.cookie.split(";");
        for (var i = 0; i < ca.length; i++) {
            var c = ca[i];
            while (c.charAt(0) == " ") {
                c = c.substring(1);
            }
            if (c.indexOf(name) == 0) {
                lsv = c.substring(name.length, c.length);
            }
        }
        if (lsv != whir.res.pageVersion || (window.localStorage && lsv != localStorage.getItem("version"))) {
            var d = new Date();
            d.setTime(d.getTime() + (365 * 24 * 60 * 60 * 1000));
            var expires = "expires=" + d.toUTCString();
            document.cookie = "lsv=;" + expires;
            location.reload();
        }
    </script>
<#else>
<script type="text/javascript" src="${__static_server__}/js/jquery-1.8.3.min.js"></script>
<script type="text/javascript" src="${__static_server__}/js/framework7/js/framework7.min.js"></script>
</#if>
<script>
    whir.res.loadJs("jquery", __BASE_SERVER__ + "/js/jquery-1.8.3.min.js");           whir.res.loadJs("framework7js", __BASE_SERVER__ + "/js/framework7/js/framework7.min.js", function () {
            myApp = new Framework7();
            $$ = Dom7;
            $.cookie('lsv', whir.res.pageVersion, {path: '/', expires: 365});
        });
</script>

localstorage.js

var whir = window.whir || {};
var refreshYN = false;
whir.res = {
    pageVersion: "121", //页面版本,由页面输出,用于刷新localStorage缓存
    //动态加载js文件并缓存
    loadJs: function (name, url, callback) {
        if (window.localStorage) {
            var xhr;
            var js = localStorage.getItem(name);
            if (js == null || js.length == 0 || this.pageVersion != localStorage.getItem("version")) {
                refreshYN = true;
                if (window.ActiveXObject) {
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } else if (window.XMLHttpRequest) {
                    xhr = new XMLHttpRequest();
                }
                if (xhr != null) {
                    xhr.open("GET", url);
                    xhr.send(null);
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            js = xhr.responseText;
                            localStorage.setItem(name, js);
                            localStorage.setItem("version", whir.res.pageVersion);
                            // 确保浏览器支持localStorage.setItem
                            if (localStorage.getItem("version") == whir.res.pageVersion) {
                                if (callback != null) {
                                    callback(); //回调,执行下一个引用
                                }
                            }
                        }
                    };
                }
            } else {
                whir.res.writeJs(js);
                if (callback != null) {
                    callback(); //回调,执行下一个引用
                }
            }
        } else {
            whir.res.linkJs(url);
        }
    },
    loadCss: function (name, url) {
        if (window.localStorage) {
            var xhr;
            var css = localStorage.getItem(name);
            if (css == null || css.length == 0 || this.pageVersion != localStorage.getItem("version")) {
                if (window.ActiveXObject) {
                    xhr = new ActiveXObject("Microsoft.XMLHTTP");
                } else if (window.XMLHttpRequest) {
                    xhr = new XMLHttpRequest();
                }
                if (xhr != null) {
                    xhr.open("GET", url);
                    xhr.withCredentials = true;
                    xhr.send(null);
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState == 4 && xhr.status == 200) {
                            css = xhr.responseText;
                            localStorage.setItem(name, css);
                            localStorage.setItem("version", whir.res.pageVersion);
                        }
                    };
                }
            } else {
                css = css.replace(/images\//g, "style/images/"); //css里的图片路径需单独处理
                whir.res.writeCss(css);
            }
        } else {
            whir.res.linkCss(url);
        }
    },
    //往页面写入js脚本
    writeJs: function (text) {
        var head = document.getElementsByTagName('HEAD').item(0);
        var link = document.createElement("script");
        link.type = "text/javascript";
        link.innerHTML = text;
        head.appendChild(link);
    },
    //往页面写入css样式
    writeCss: function (text) {
        var head = document.getElementsByTagName('HEAD').item(0);
        var link = document.createElement("style");
        link.type = "text/css";
        link.innerHTML = text;
        head.appendChild(link);
    },
    //往页面引入js脚本
    linkJs: function (url) {
        var head = document.getElementsByTagName('HEAD').item(0);
        var link = document.createElement("script");
        link.type = "text/javascript";
        link.src = url;
        head.appendChild(link);
    },
    //往页面引入css样式
    linkCss: function (url) {
        var head = document.getElementsByTagName('HEAD').item(0);
        var link = document.createElement("link");
        link.type = "text/css";
        link.rel = "stylesheet";
        link.rev = "stylesheet";
        link.media = "screen";
        link.href = url;
        head.appendChild(link);
    }
};

第三阶段

对于跨域的处理,我们采用了CORS的方式,主要就是在Nginx服务器上配置一下,比较简便。