天天看点

extjs中js资源缓存策略

浏览器对静态文件的缓存主要是通过cache-control来控制的,cache-control可以设置no-cache,max-age以及must-revalidate等来设置缓存策略。

如果max-age> 0则会在max-age的时间内不访问服务器,用本地缓存的静态文件代替。

如果max-age<=0则会每次都询问服务器,资源文件是否有修改,有则200,无则304。这相当于f5操作。

no-cache表示不理会缓存协商,全部重新加载。这相当于是ctrl+f5操作。

must-revalidate表示必须遵循策略规则。因为浏览器有时候会提取过期的缓存,设置了这个策略后,浏览器会严格遵循客户设置的策略。

服务器在首次应答客户的request时,会返回last-modified和etag给浏览器,浏览器将cache住这些信息,下一次request服务器时就会在header里带上if-modified-since和if-non-match信息,这两个分别对应last-modified和etag。服务器会提取对应资源的modified时间和etag来做对比,如果有改变则返回200并且response last-modified和etag给客户端,没变则返回304不需要改变。

目前tomcat已经支持etag, tomcat是根据文件的contentlength和lastmodified混合编码生成串儿。因为last-modified因为只支持到秒级,所以对于秒内频繁修改的静态资源效率会比较低下,etag则很好的避免了这一点。

一般对于静态资源,服务器端会通过过滤机制,自动为响应的header里添加max-age信息,这样浏览器就会在本地cache住这些资源。

我们最近的一个项目,前台使用extjs,使用extjs带来的成本就是所有的页面几乎都是js页面了。因为静态js的文件量大,且我们系统的运营地点非洲网络条件不太好,带宽资源比较宝贵,不能承受频繁的静态资源请求(这里需要提一点,即使是web服务器最终响应不需要更改的304请求,对系统也是一次带宽开销,我们也想尽力避免),于是我们想通过cache-contro将js文件cache在本地。但是这就带来一个问题,对于紧急发布的一些前台界面,因为超时机制会无法及时在系统层面体现。

所以我们必须实现一种机制,在发布了新的js后,对应的引用该js的地方都要能自动刷新。所以最简单的方法就是每次编译完后生成一个版本号,然后对每个引用js的url都添加上版本号就ok。这样就可以保证在一次发布周期内始终只有一个js版本。

extjs实现了一套动态加载策略,可以通过js语言的方式去直接引入一个资源(ext.require),这和我们平时使用的静态引用(<script src=" xxx")是完全不同的。所以决定研究一下extjs的动态加载机制。

目前网上有开源的extjs4的源码,在动态load script时,会读取config配置信息,config可以通过loader.setconfig来配置。config里有两个参数需要注意,一个是disablecachingparam还有一个是disablecaching,后者代表是否需要为每一个请求都声称一个版本信息,前者是版本信息的参数名,默认是_dc.

loadscriptfile: function(url, onload, onerror, scope, synchronous) {

            if (isfileloaded[url]) {

                return loader;

            }

            var config = loader.getconfig(),

                nocacheurl = url + (config.disablecaching ? ('?' + config.disablecachingparam + '=' + ext.date.now()) : ''),

                iscrossoriginrestricted = false,

            if (!synchronous) {

                onscripterror = function() {

                    //<debug error>

                    onerror.call(scope, "failed loading '" + url + "', please verify that the file exists", synchronous);

                    //</debug>

                };

                scriptelements[url] = loader.injectscriptelement(nocacheurl, onload, onscripterror, scope);

            } else {

                /**

                * 组装xmlhttprequest对象,根据浏览器支持情况初始化activexobject或者xmlhttprquest

                */

                try {

                    xhr.open('get', nocacheurl, false);

                    xhr.send(null);

                } catch (e) {

                    iscrossoriginrestricted = true;

                }

* 伪代码,跨域失败,执行onerror回调函数

*/

                else if ((status >= 200 && status < 300) || (status === 304)

                    //<if isnonbrowser>

                    || isphantomjs

                    //</if>

                ) {

                    //调用成功, 记录调试信息

           ext.globaleval(xhr.responsetext + debugsourceurl);

                    onload.call(scope);

                else {

                   /**

   * 伪代码,执行onerror回调函数

   */

                // prevent potential ie memory leak

                xhr = null;

        }

代码很好理解,同步时直接加载,并且通过eval方法直接执行响应的js文件(responsetext),异步时则通过injectscriptelement加载,同步还是异步可以通过loader里的syncmodeenabled来设置,默认是false。

注意下这个方法里的nocacheurl,根据disablecaching决定要不要为url加上版本信息,缓存无效时会默认以当前时间为版本号。

injectscriptelement: function(url, onload, onerror, scope, charset) {

            var script = document.createelement('script'),

                dispatched = false,

* 设置onload和onerror的回调函数,主要是通过dispatched参数做幂等性校验

                * 为了兼容ie,script需要支持onreadystatechange事件,这个事件会又多个状态,所以需要做幂等校验

            script.type = 'text/javascript';

            script.onerror = onerrorfn;

            charset = charset || config.scriptcharset;

            if (charset) {

                script.charset = charset;

            //兼容ie

            if ('addeventlistener' in script ) {

                script.onload = onloadfn;

            } else if ('readystate' in script) {   // for <ie9 compatability

                script.onreadystatechange = function() {

                    if ( this.readystate == 'loaded' || this.readystate == 'complete' ) {

                        onloadfn();

                    }

                 script.onload = onloadfn;

            script.src = url;

            (loader.documenthead || document.getelementsbytagname('head')[0]).appendchild(script);

            return script;

该方法主要是创建了一个script的元素,url是前面方法生成的。

因为我们需要的是基于发布版本信息的缓存,所以只需要将nocacheurl的版本信息由当前时间戳改成编译产生的版本信息即可。

最后,注意,因为extjs6做了比较大的重构,url生成挪到了ext.data.proxy.server里的buildurl里:

url = ext.urlappend(url, ext.string.format("{0}={1}", me.getcachestring(), ext.date.now())),搜索这一行即可。

继续阅读