为了绕开浏览器的单域名下的cookie数限制,一些开发人员使用了一种称为子cookie(subcookie)的概念。子cookie是存放在单个cookie中的更小段的数据。也就是使用cookie值来存储多个名称值对儿。子cookie最常见的格式如下所示:
name=name1=value1&name2=value2&name3=value3&name4=value4&name5=value5
子cookie一般也可以查询字符串的格式进行格式化。然后这些值可以使用单个cookie进行储存和访问,而非对每个名称-值对儿使用不同的cookie存储。最后网站或者Web应用程序可以无需达到单域名cookie上限也可以存储更加结构化的数据。
为了更好地操作子cookie,必须建立一系列新的方法。子cookie的解析和序列化会因子cookie的期望用途而略有不同并更加复杂些。例如,要获得一个子cookie,首先要遵循并获得cookie一样的基本步骤,但是在解码cookie值之前,需要按如下方法找出子cookie的信息。
var SubCookieUtil = { get: function (name, subName) { var subCookies = this.getAll(name); if (subCookies) { return subCookies[subName]; } else { return null; } }, getAll: function (name) { var cookieName = encodeURIComponent(name) + "=", cookieStart = document.cookie.indexOf(cookieName), cookieValue = null, cookieEnd, subCookies, i, parts, result = {}; if (cookieStart > -1) { cookieEnd = document.cookie.indexOf(";", cookieStart); if (cookieEnd == -1) { cookieEnd = document.cookie.length; } cookieValue = document.cookie.substring(cookieStart + cookieName.length, cookieEnd); if (cookieValue.length > 0) { subCookies = cookieValue.split("&"); for (i = 0, len = subCookies.length; i < len; i++) { parts = subCookies[i].split("="); result[decodeURIComponent(parts[0])] = decodeURIComponent(parts[1]); } return result; } } return null; }, //省略了更多的代码 };
获取子cookie的方法有两个:get()和getAll()。其中get()获取单个子cookie的值,getAll()获取所有子cookie并将它们放入一个对象中返回,对象的属性为子cookie的名称,对应值为子cookie对应的值。get()方法接收两个参数:cookie的名字和子cookie的名字。它其实就是调用getAll()获取所有的子cookie,然后只返回所需的那一个(如果cookie不存在则返回null)。
SubCookieUtil.getAll()方法和CookieUtil.get()在解析cookie值的方式上非常相似。区别在于cookie的值并非立即解码,而是先根据&字符将子cookie分割出来放在一个数组中,每一个子cookie再根据等于号分割,这样在parts数组中的前一部分便是子cookie名,后一部分则是子cookie的值。这两个项目都要使用decodeURIComponent()来解码,然后,放入result对象中,最后作为方法的返回值。如果cookie不存在,则返回null。
可以像下面这样使用上述方法:
//假设document.cookie=data=name=Nicholas&book=Professional%20JavaScript //取得全部子cookie var data = SubCookieUtil.getAll("data"); alert(data.name); //"Nicholas" alert(data.book); //"Professional JavaScript" //逐个取得子cookie alert(SubCookieUtil.get("data", "name")); //"Nicholas" alert(SubCookieUtil.get("data", "book")); //"Professional JavaScript"
要设置子cookie,也有两种方法:set()和setAll()。以下代码展示了它们的构造。
var SubCookieUtil = { set: function (name, subName, value, expires, path, domain, secure) { var subcookies = this.getAll(name) || {}; subcookies[subName] = value; this.setAll(name, subcookies, expires, path, domain, secure); }, setAll: function (name, subcookies, expires, path, domain, secure) { var cookieText = encodeURIComponent(name) + "=", subcookieParts = new Array(), subName; for (subName in subcookies) { if (subName.length > 0 && subcookies.hasOwnProperty(subName)) { subcookieParts.push(encodeURIComponent(subName) + "=" + encodeURIComponent(subcookies[subName])); } } if (subcookieParts.length > 0) { cookieText += subcookieParts.join("&"); if (expires instanceof Date) { cookietext += "; expires=" + expires.toGMTString(); } if (path) { cookieText += ";path=" + path; } if (domain) { cookieText += "; domain=" + domain; } if (secure) { cookieText += "; secure"; } } else { cookieText += "; expires=" + (new Date(0).toGMTString()); } document.cookie = cookieText; }, //省略了更多代码 };
这里的set()方法接收7个参数:cookie名称、子cookie名称、子cookie值、可选的cookie失效日期或时间的Date对象、可选的cookie路径、可选的cookie域和可选的布尔secure标志。所有的可选参数都是作用于cookie本身而非子cookie。为了在同一个cookie中存储多个子cookie,路径、域和secure标志必须一致;针对整个cookie的失效日期则可以在任何一个单独的子cookie写入的时候同时设置。在这个方法中,第一步是获取指定cookie名称对应的所有子cookie。逻辑或操作符”||”用于当getAll()返回null时将subcookies设置为一个新对象。然后,在subcookies对象上设置好子cookie值并传给setAll()。
而setAll()方法接收6个参数:cookie名称、包含所有子cookie的对象以及和set()中一样的4个可选参数。这个方法使用for-in循环遍历第二个参数中的属性。为了确保确实是要保存的数据,使用了hasOwnProperty()方法,来确保只有实例属性被序列化到子cookie中。由于可能会存在属性名为空字符串的情况,所以在把属性名加入结果对象之前还要检查一下属性名的长度。将每个子cookie的名值对儿都存入subcookieParts数组中,以便稍后可以使用join()方法以&号组合起来。剩下的方法则和CookieUtil.set()一样。
可以按如下方式使用这些方法:
//假设document.cokie=data=name=Nicholas&book=Professiona%20JavaScript //设置两个cookie SubCookieUtil.set("data", "name", "Nicholas"); SubCookieUtil.set("data", "book", "Professional JavaScript"); //设置全部子cookie和失效日期 SubCookieUtil.setAll("data", { name: "Nicholas", book: "Professional Javascript" }, new Data("January 1, 2010")); //修改名字的值,并修改cookie的失效日期 SubCookieUtil.set("data", "name", "Michael", new Date("February 1, 2010"));
子cookie的最后一组方法是用于删除子cookie的。普通cokie可以通过将失效时间设置为过去的时间的方法来删除,但是子cookie不能这样做。为了删除一个子cookie,首先必须获取包含在某个cookie中的所有子cookie,然后仅删除需要删除的那个子cookie,然后再将余下的子cookie的值保存为cookie的值。请看以下代码。
var SubCookieUtil = { unset: function (name, subName, path, domain, secure) { var subcookies = this.getAll(name); if (subcookies) { delete subcookies[subName]; this.setAll(name, subcookies, null, path, domain, secure); } }, unsetAll: function (name, path, domain, secure) { this.setAll(name, null, new Date(0), path, domain, secure); } };
这里定义的两个方法用于两种不同的目的。unset()方法用于删除某个cookie中单个子cookie而不影响其它的;而unsetAll()方法则等同于CookieUtil.unset(),用于删除整个cookie。和setAll()一样、域和secure标志必须和之前创建的cookie包含的内容一致。这两个方法可以像下面这样使用。
//仅删除名为name的子cookie SubCookieUtil.unset("data", "name"); //删除整个cookie SubCookieUtil.unsetAll("data");
如果你担心开发中可能会达到单域名的cookie上限,那么子cookie可是一个非常有吸引力的备选方案。不过,你需要更加密切关注cookie的长度,以防超过单个cookie的长度限制。