【我爱设计模式】备忘录 - Ajax响应缓存

备忘录模式,是我最喜欢使用的几个设计模式之一,实用性很强,我已经多次运用在项目中。

我最为常用的实践方式,就是 用于 Ajax 缓存。

这个一直是我做项目的一个必备优化点

可以减少请求,减轻服务器压力,优化用户体验

怎么优化的呢,简单描述?

把请求过的数据,缓存在一个变量里面,然后每次发请求的时候,先找缓存,如果有缓存,就从缓存获取,否则就发请求。

恩,大概是这个流程,那到底是这么实现的,请大家往下看

提醒一下,文章篇幅有点长(都是代码),但是很简单,也很有用,看完的人,肯定会对你有帮助

备忘录模式

"在不破坏对象的封装性的前提下,在对象之外捕获并保存该对象内部的状态,以便日后对象使用或者对象恢复到以前的某个状态。”

—— 张容铭【JavaScript设计模式】

额.......有点复杂的...

按我的理解就是

使用变量保存数据。为什么叫备忘录呢,因为是对数据进行备份,把数据 放到 备忘录对象 的缓存器中,因而称为 备忘录模式

备忘录对象

其实就是 JS 的一个对象变量。而为了避免数据被篡改,需要对数据进行一定程度的隐藏性封装。所以最好作为闭包存在,然后提供方法对它进行访问和修改 即可。

缺点

当保存的数据过多的时候,会严重占用系统提供的资源,极大降低系统性能

所以不推荐什么数据都进行缓存,一般是没有时效性的数据。但是这个缓存作为变量存在,关闭网页就没有了,所以也不会出现啥

综上,这是个 利大于弊的 做法

缓存机制

嗯....缓存机制听起来好高大上,就是说明一下,缓存怎么存放.....

缓存的重点

1、 存储对象 2、如何存放

1、备忘录存储对象

恩,下面是我进行数据存放的对象 CACHE

var ReqCache = (function() {    
    var CACHE={}
})()

恩,你可以看到,我使用了一个闭包,里面有一个变量 CACHE ,这个是专门存放数据的一个闭包对象

2、数据存放方法

一个对象,如果我要往里面填数据,我肯定需要设定一个 key

比如 我把 {name:1} 存放到 CACHE 中,需要这样

CACHE['a'] = { name:1 }

这样,数据就被保存下来了,然后我拿到 a 这个key,我就能从缓存中获取数据

是不是挺简单的,我就是利用这个简单的原理,来实现了这个 缓存方法

实现重点 就在

我如何 设置这个 key 才能保存唯一性

导致每个请求的不同的无非就是是是三个因素

1、请求方法,2、请求参数,3、请求 url

所以我就以这因素作为 key(当然,还有一个 header 的因素,但是我再三考虑下,觉得好像不太通用,所以就没考虑)

1

请求方法 作为 作为 第一层 key

比如,先添加 POST 如下,其他请求方法也一样

var CACHE={    
    POST:{}
}

2

请求 url 作为第二层 key

比如你的请求url 是

http://www.baidu.com/search/getxxxxx

我就会 这样放置

var CACHE={    
    POST:{        
        "http://www.baidu.com/search/getxxxxx":{}
   }
}

但是,考虑到url 中一些符号和字母没有用,所以我会使用正则进行过滤 url

"http://www.baidu.com/search/getxxxxx"

过滤成

"wwwbaiducomsearchgetxxxxx"

恩,为什么呢,为了 减少 key 的长度,从而减少数据总量

我是使用了这个方法进行过滤

function getFilterUrl(url) {    
    return   url.replace(/\.|\/|:|#|=|&|\?|-|(http|https)/g, "")
}

3

请求参数 作为第三层key

比如你的请求参数是

{page:1,pageNum:20,type:80}

我直接 把 参数 序列化,变成 字符串

"{page:1,pageNum:20,type:80}"

这样,整个 CACHE 对象基本成型

var CACHE={    
    POST:{        
        "http://www.baidu.com/search/getxxxxx":{            
            "{page:1,pageNum:20,type:80}": 请求的响应数据    
        }
   }
}

你有没有搞错,参数如果顺序调换一下怎么办,那还不是找不到数据,还要请求,而且会存储重复数据?

别怕,我也有考虑,所以我把 参数 先排序了一遍,再进行序列化的

参数排序

我自己写了一个方法,进行请求参数排序

function objKeySort(obj) {    
    var newkey = Object.keys(obj).sort();    
   var newObj = {};
   for (var i = 0; i < newkey.length; i++) {
       newObj[newkey[i]] = obj[newkey[i]];
   }    
    return newObj;
}

好的,我们实践一下,结果如何好吧

objKeySort({    
    acd:"1", abc:"2", aac:"3", aaa:"9",   
    a81:"888",az9:"888", az8:"888",a79:"888"
})

得到下面的数据

按我实践经历来说,应该是真货,我也没有实践太多,如果看到的观众发现有什么问题的,请及时跟我联系,我要在领导没有发现之前,及时修复这个bug,有重谢

过滤没必要的参数

考虑到 请求参数中也有可能会存在一个没必要的 参数,徒增我们的对象大小

所以我会在 ReqCache 里面设置一个方法,来过滤 请求参数

function getFilterReqParams(data) {     
     var newData = {}        
    for (var i in data) {         
         var item = data[i]         
         var typeItem = typeof item    
        if(typeItem=="object" && item!=null){
            newData[i]=getFilterReqParams(item)
        }         
         // 找不到的时候赋值
        else if(FILTER_KEYS.indexOf(i)===-1){
            newData[i]=item
        }
    }     
     return newData
}

但是,要过滤什么参数呢??

比如 有的请求需要带 token 什么的,这个每个请求都有,没什么参考意义,所以我决定去掉

同时,我们会暴露一个方法出来,供用户来设置 过滤的参数

var ReqCache = (function() {    
    var FILTER_KEYS=[]    
    return  {
       setFilterKeys(key){            
            if(Array.isArray(key))FILTER_KEYS = key            
            else FILTER_KEYS = [key]
       }
   }
})()
// 然后你在外部 这么调用,求参数就被 过滤掉 token 和 userName 了
ReqCache.setFilterKeys(['token','userName'])

TIP

FILTER_KEYS,我同样作为闭包存在,方法没有做太多的错误处理,你可以处理完善一点,我觉得在项目中不会自己乱传参数就算了

过滤没必要的字符

同样,请求参数变成字符串,"{name:1,id:23}",里面同样有很多 多余的字符,没必要留下来,像是 " { , : ",都需要去掉

data = data.replace(/\.|\/|:|\{|\}|"|\?|,/g, "")

4

组装获取最终 key

恩,总结一下上面的 要点,就是如何 给 缓存设置 唯一并且合适 的 key,用来保存/获取缓存

设置 key 的 因素

  1. 请求方法,get,post 等
  2. 请求 url,需要过滤
  3. 请求参数 ,需要过滤并排序

然后我会统一通过一个方法,通过传入 method,data,url,来组装 返回拿到最终合适的 key,方法如下

function  getFilterKey(param){    
   var data = JSON.stringify(objKeySort(getFilterReqParams(param.data)))
   data = data.replace(/\.|\/|:|\{|\}|"|\?|,/g, "")

    return {
       data,        
        url:getFilterUrl(param.url),        
        method:param.method.toUpperCase()
   }
}

缓存操作

我们会提供三个方法

添加缓存,获取缓存,删除缓存(本来想加一个更新缓存,感觉可以使用添加缓存直接覆盖,就算了)

我们来一个个讲解方法

添加缓存

需要两个参数

1、param,一个对象,包含 method,data,url

2、backData,请求的响应

传入一个对象param,包含 method,data,url。使用 getCurrentKey 组装 param 拿到 key,然后逐层设置

CACHE[ method ][ url ][ data ] = resData

function setCache  (params,backData) {            
   var {method,url,data} =getCurrentKey(params)    
    var urlCache
   urlCache=CACHE[method]    
    
    if(typeof urlCache[url] =="undefined")  urlCache[url]= {}
   urlCache[url][data] = backData    
}

获取缓存

传入一个对象param,包含 method,data,url。使用 getCurrentKey 组装 param 拿到 key,然后逐层获取对象

var data = CACHE[ method ][ url ][ data ] 便是最终你缓存的数据

function getCache  (params) {    
    var {method,url,data} =getCurrentKey(params)    
    var urlCache = CACHE[method][url] || {}    
   return urlCache[data]
},

删除缓存

也是一样,使用 getCurrentKey 拿到 key,到了 最后一层,直接 delete

delete CACHE[ method ][ url ][ data ]

function delCache  (params) {    
    var {method,url,data} =getCurrentKey(params)    
    var urlCache = CACHE[method][url]    
    var dataCache= urlCache && urlCache[data]
   
   dataCache && delete urlCache[data]
}

其实我从来没有删除过缓存,只是为了增删查 都有,所以干脆写全了。

讲了好多,会有一些乱,我们现在继续总结

1、CACHE 是一个对象,保存数据

2、CACHE 的 key: method,过滤的 url,过滤排序的 data

3、url,需要去掉 多余的 字符

4、参数,需要去掉多余字符,并进行排序

5、编写方法 getCurrentKey 用于统一组装 key,返回 过滤排序后的 url,method,data

6、暴露 getCache ,setCache,delCache 来对 CACHE 对象进行操作

缓存使用

说了这么多,到底怎么使用呢??

假设你封装有一个 Ajax 函数,用于发送请求前处理一下,而且所有请求都会调用这个函数。

现在我简单实现一下 Ajax,并把 ReqCache 使用步骤写上来

TIP

当然不可能把所有请求的响应都进行缓存,我一般是将 不太可能变化的数据进行缓存。

此时,通过一个 参数 cache ,判断决定此次请求是否需要缓存即可

function Ajax(param){    
    // 如果请求需要缓存,那么就先查看是否存在缓存,存在就直接返回
    if(param.cache){           
           var cache = ReqCache.getCache({                
               method:param.method,                
               data:param.data,                
               url:param.url
          })           
           if(cache) return cache
    }    
    // 这个是真正发送请求的函数
    sendAjax({
          success(resData){                
               // 如果请求需要缓存,在请求成功函数中把响应保存起来
               if(param.cache){
                    ReqCache.setCache({                           
                           method:param.method,                           
                           data:param.data,                           
                           url:param.url
                    },resData)
               }
          }
    })
}

我的源码

附上我的完整的,已经在项目中使用了的代码

var ReqCache = (function() {    

   // 请求缓存对象
   var CACHE ={        
        "GET":{}, // get 请求的接口数据
       "POST":{}, // post 请求的接口数据
   }    
   var FILTER_KEYS=[]  
          
   // 过滤不太重要的请求参数 作为 缓存 key
   function getFilterReqParams(data) {    
       var newData = {}            
       for (var i in data) {        
           var item = data[i]            
            var typeItem = typeof item    
           if(typeItem=="object" && item!=null){
               newData[i]=getFilterReqParams(item)
           }    
           // 找不到的时候赋值
           else if(FILTER_KEYS.indexOf(i)===-1){
               newData[i]=item
           }
   
       }        
        return newData
   }    
        
   // 过滤路径中不要重要的符号 作为 缓存的 key
   function getFilterUrl(url) {        
        return   url.replace(/\.|\/|:|#|=|&|\?|-|(http|https)/g, "")
   }    
        
   // 对象按字母排序
   function objKeySort(obj) {        
        var newkey = Object.keys(obj).sort();      
     
       var newObj = {};
       for (var i = 0; i < newkey.length; i++) {
           newObj[newkey[i]] = obj[newkey[i]];
       }        
        return newObj;
   }    
       
   // 过滤请求的参数,返回一个适合作为 key 保存的 字符串
   function  getCurrentKey(param){    
       var data = JSON.stringify(objKeySort(getFilterReqParams(param.data)))
       data = data.replace(/\.|\/|:|\{|\}|"|\?|,/g, "")
         
        return {
           data,            
            url:getFilterUrl(param.url),            
            method:param.method.toUpperCase()
       }
   }        
   return {        
       // 设置需要过滤的请求参数
       setFilterKeys(key){            
            if(Array.isArray(key))FILTER_KEYS = key            
            else FILTER_KEYS = [key]
       },    
       // 设置缓存
       setCache  (params,backData) {            
           var {method,url,data} = getCurrentKey(params)            
            var urlCache
   
           urlCache=CACHE[method]            
            if(typeof urlCache[url] =="undefined")  urlCache[url]= {}
               
           urlCache[url][data] = backData
       },    
       // 获取缓存;
       getCache  (params) {    
           var {method,url,data} = getCurrentKey(params)            
            var urlCache = CACHE[method][url] || {}            
           return urlCache[data]
       },    
       // 删除缓存
       delCache  (params) {    
           var {method,url,data} = getCurrentKey(params)            
            var urlCache = CACHE[method][url]            
            var dataCache= urlCache && urlCache[data]
           
           dataCache && delete urlCache[data]
       }
   }
})()

现场测试

使用 ReqCache 存放一条数据,并设置过滤 key

ReqCache.setFilterKeys(['name'])
var resData = {  age:18,type:"靓仔",description:"有前途的靓仔"}
ReqCache.setCache({    
   method:"post",    
   data:{        
       id:1,name:"神仙朱"
   },    
   url:"http://www.baidu.com/serarch/name"

}, resData)

然后我在内部打印 CACHE,可以看到

最后

好的,其实这个很简单,而且非常实用和 适用各个项目,只要你的项目需要发http请求,而你不需要做什么改变,即插即用

只需要往 封装的请求函数中一放,缓存就好了,真的非常好用,这个也是我做项目自己实践出来的,不过我不知道网上有没有类似的,反正是我自己想的,哈哈哈

原文发布于微信公众号 - 神仙朱(skying-zhu)

原文发表时间:2018-12-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券