提到cookie,大家都不会陌生的,几乎涉及到交互或统计的WEB系统都会使用到cookie,关于cookie的基础知识网上也有很多,这里推荐两篇文章:
聂微东的: http://www.cnblogs.com/Darren_code/archive/2011/11/24/Cookie.html#2933759
张子秋的:http://www.cnblogs.com/zhangziqiu/archive/2009/08/06/cookies-javascript-aspnet.html
cookie,很实用,但是在各大浏览器里面限制比较多,下面是各浏览器对cookie大小和个数的限制:
IE6.0 | IE7.0/IE8.0 | Opera | FF | Safari | Chrome | |
cookie个数 | 每个域为20个 | 每个域为50个 | 每个域为30个 | 每个域为50个 | 没有个数限制 | 每个域为53个 |
cookie大小 | 4095个字节 | 4095个字节 | 4096个字节 | 4097个字节 | 4097个字节 | 4097个字节 |
看到这张表,突然感到好忧伤,因为我们的项目还要一直兼容IE6,IE在Cookie方面只能保留20个,较旧的会被清掉。 表中Opera是30个,但是新版的Opera已经开始使用webkit内核了,应该也是50个以上的了。
今天我们来讨论的就是解决cookie个数太多的问题,我的做法就是将多个cookie压缩在一个cookie里面 ,里面涉及到很多问题,如域、路径、有效期等,下来直接看一下源码先:
1 /** 2 * cookie操作模块 3 * @name cookiex 4 * @function 5 * @param {String} key 6 * @param {String} [value] 7 * @param {{expires:Number|'forever', domain:String, path:String , inCommon: boolean , secure : boolean}} [options] 8 * @reutrns {String} 9 * @example 10 * cookiex(key); //读取cookie 11 * cookiex(key, null); //删除cookie 12 * cookiex(key, value, {"expires":forever,"domain":"mydomain.com","path":"/" , inCommon : false }); //设置cookie 13 */ 14 function cookiex(key,value,options){ 15 16 var options = options || {}, 17 ret, 18 result, 19 getRet = function(tkey){ 20 var tret = ''; 21 if(result = new RegExp('(?:^|; )' + encodeURIComponent(tkey) + '=([^;]*)').exec(document.cookie)) { 22 try { 23 tret = decodeURIComponent(result[1]); 24 } catch(e) { 25 tret = result[1]; 26 } 27 } 28 return tret; 29 }; 30 31 32 // key and value given, set cookie... 33 if (arguments.length > 1 && (value === null || typeof value !== "object")) { 34 35 if (value === null) { 36 37 38 //删除forever里面的东西,只改key和expires,在最后才写cookie 39 var forever = getRet("my_common_forever"), 40 inCommon = false, 41 oldKey = key, 42 oldDomain = options.domain , 43 oldPath = options.path; 44 45 46 if(forever != ''){ 47 var foreverList = forever.split('&'); 48 for(var i = 0; i < foreverList.length; i++){ 49 if(foreverList[i].split('=')[0] == key){ 50 foreverList.splice(i,1); 51 key = "my_common_forever"; 52 value = foreverList.join("&"); 53 options.expires = new Date(0xfffffffffff); 54 options.domain = "mydomain.com"; 55 options.path = "/"; 56 inCommon = true; 57 break; 58 } 59 } 60 } 61 62 //删除session里面的东西,只改key和expires,在最后才写cookie 63 if(!inCommon){ 64 var session = getRet("my_common_session"); 65 if(session != ''){ 66 var sessionList = session.split('&'); 67 for(var i = 0; i < sessionList.length; i++){ 68 if(sessionList[i].split('=')[0] == key){ 69 sessionList.splice(i,1); 70 key = "my_common_session"; 71 value = sessionList.join("&"); 72 options.expires = null; 73 options.domain = "mydomain.com"; 74 options.path = "/"; 75 inCommon = true; 76 break; 77 } 78 } 79 } 80 } 81 82 //如果不在公用cookie里面就直接在最后删除原生cookie就行了,如果在公用cookie里面就改变 83 if(!inCommon){ 84 value = ''; 85 options.expires = new Date(0); 86 }else{ 87 //删除原生cookie,带直接写cookie操作 88 document.cookie = [ 89 encodeURIComponent(oldKey), 90 '=', 91 "" , 92 '; expires=' + (new Date(0)).toGMTString(), 93 oldPath ? '; path=' + oldPath : '; path=/', 94 oldDomain ? '; domain=' + oldDomain : '; domain=mydomain.com', 95 options.secure ? '; secure' : '' 96 ].join(''); 97 } 98 }else{ 99 //写cookie 100 if (typeof options.expires === 'number') { 101 //计时cookie,默认以秒计 102 var minutes = options.expires, 103 t = options.expires = new Date(); 104 t.setDate(t.getSeconds() + minutes); 105 } else if(typeof options.expires === 'string' && options.expires != 'forever') { 106 //计时cookie ,根据开发者输入的尾缀定单位 107 var t = parseInt(options.expires), 108 suffix = options.expires[options.expires.length -1], 109 now = new Date();; 110 if(suffix=="s"){ 111 now.setSeconds(now.getSeconds() + t); 112 options.expires = now; 113 } else if(suffix=="m") { 114 now.setMinutes(now.getMinutes() + t); 115 options.expires = now; 116 } else if(suffix=="h") { 117 now.setHours(now.getHours() + t); 118 options.expires = now; 119 } else if(suffix=="d") { 120 now.setDate(now.getDate() + t) 121 options.expires = now; 122 } else if(suffix=="M") { 123 now.setMonth(now.getMonth() + t); 124 options.expires = now; 125 } 126 } else if(options.expires == 'forever') { 127 //永久cookie 128 options.expires = new Date(0xfffffffffff); 129 if(options.inCommon){ 130 //如果使用公用cookie,强制使用domain : .mydomain.com这个和 path : / 131 options.domain = "mydomain.com"; 132 options.path = "/"; 133 var forever = cookiex("my_common_forever"); 134 if(forever!=''){ 135 136 var retList = forever.split('&'), 137 inForever = false; 138 139 for(var i = 0; i < retList.length; i++){ 140 if(retList[i].split('=')[0] == key){ 141 retList[i] = key + '=' + encodeURIComponent(String(value)); 142 inForever = true; 143 break; 144 } 145 } 146 147 if(!inForever){ 148 value = forever + "&" + key + '=' + encodeURIComponent(String(value)); 149 }else{ 150 value = retList.join('&'); 151 } 152 153 }else{ 154 value = key + '=' + encodeURIComponent(String(value)); 155 } 156 key = "my_common_forever"; 157 } 158 } else if( typeof options.expires === "object" && (a instanceof Date)){ 159 //用户自己传入Date对象 160 } else { 161 //浏览器进程cookie 162 options.expires = null; 163 if(options.inCommon){ 164 //如果使用公用cookie,强制使用domain : .mydomain.com这个和 path : / 165 options.domain = "mydomain.com"; 166 options.path = "/"; 167 var session = cookiex("my_common_session"); 168 if(session!=''){ 169 170 var retList = session.split('&'), 171 inSession = false; 172 173 for(var i = 0; i < retList.length; i++){ 174 if(retList[i].split('=')[0] == key){ 175 retList[i] = key + '=' + encodeURIComponent(String(value)); 176 inSession = true; 177 break; 178 } 179 } 180 181 182 if(!inSession){ 183 value = session + "&" + key + '=' + encodeURIComponent(String(value)); 184 }else{ 185 value = retList.join('&'); 186 } 187 }else{ 188 value = key + '=' + encodeURIComponent(String(value)); 189 } 190 key = "my_common_session"; 191 } 192 } 193 194 } 195 196 197 //执行操作 198 return (document.cookie = [ 199 encodeURIComponent(key), '=', 200 options.raw ? String(value) : encodeURIComponent(String(value)), 201 options.expires ? '; expires=' + options.expires.toGMTString() : '', // use expires attribute, max-age is not supported by IE 202 options.path ? '; path=' + options.path : '; path=/', 203 options.domain ? '; domain=' + options.domain : '; domain=mydomain.com', 204 options.secure ? '; secure' : '' 205 ].join('')); 206 } 207 208 209 // key and possibly options given, get cookie... 210 options = value || {}; 211 ret = getRet(key); 212 213 //查找永久公用cookie 214 if(ret == '' && key != 'my_common_forever') { 215 ret = getRet("my_common_forever"); 216 if(ret != ''){ 217 var retList = ret.split('&'); 218 for(var i = 0; i < retList.length; i++){ 219 if(retList[i].split('=')[0] == key){ 220 ret = decodeURIComponent(retList[i].split('=')[1]); 221 return ret ; 222 } 223 } 224 } 225 ret = ''; 226 } 227 228 //查找浏览器进程公用cookie 229 if(ret == '' && key != 'my_common_session') { 230 ret = getRet("my_common_session"); 231 if(ret != ''){ 232 var retList = ret.split('&'); 233 for(var i = 0; i < retList.length; i++){ 234 if(retList[i].split('=')[0] == key){ 235 ret = decodeURIComponent(retList[i].split('=')[1]); 236 return ret ; 237 } 238 } 239 } 240 ret = ''; 241 } 242 243 return ret; 244 }
一看,操作个cookie两百多行代码,有点小多,这里我来一个个解释一下,为什么会有这么多代码。
公用cookie :
这里我使用了两个公用的cookie,my_common_session(跟随浏览器进程)和my_common_forever(永久cookie),里面的多个cookie使用 & 符号连接起来,然后用 encodeURIComponent 进行编码。
我把公用cookie的域写死在一个域和路径里面了,这样可以保证读写公用cookie的时候不会因为输入的域不同导致调用不到,一般都是网站的顶级域名,可以按照自己的需求修改。
cookie添加或修改:
在cookie添加或修改的时候判断inCommon 是否为true,如果是的话,就往对应的公用cookie里面添加,否则便以传统的方式添加或修改cookie。
在添加进公用cookie的时候,需要注意的是公用cookie里面是不是已经存在有了,如果不存在就进行添加,如果之前有过的了,便进行值替换,具体是在代码的第 126~192行间。
添加非公用cookie时跟普通的cookie操作差不多,这里也提供了很多关于时间的参数。
/* 调用案例 */ cookiex("a","value-of-cookie-a"); cookiex("a"); //"abcc" cookiex("b","value-of-cookie-b",{inCommon:true}); cookiex("b"); // "bbbb" cookiex("c","value-of-cookie-c",{expires:"forever",inCommon:true}); cookiex("c"); //"value-of-cookie-c" cookiex("d","value-of-cookie-d",{expires:"3h"}); cookiex("d"); //"value-of-cookie-d"
cookie删除:
关于cookie删除操作,这里有两步操作,一步是判断公用cookie里面有没有对应的key,有的话便删除,第二步是删除非公用cookie里面对应的cookie。
/* 调用案例 */ cookiex("d",null)
可能存在问题及解决:
一个项目里面有不同人对cookie进行操作的时候,可能会同一个key的cookie即存在公用的cookie里面,又存在非公用的cookie里面。在上面的方法里是优先读取了非公用的cookie的,但是这样重复存在难免会造成一些混淆,所以解决方案是团队内部沟通好,约定好一些cookie怎么用。
与服务端结合:
cookie被这样压缩后,服务端就不能直接通过传统的方式去读取这些cookie了,所以我们可以在后台使有类似的方式封装一个方法对cookie进行相应的读写操作。