JavaScript的getCookie函数

33
我发现了两个使用JavaScript获取cookie数据的函数,一个在w3schools.com上,另一个在quirksmode.org上。
我想知道应该使用哪一个?
例如,我相信我曾经在某个地方读到过,有些浏览器会在分号;处出现问题?
w3schools:
function getCookie(c_name) {
    if (document.cookie.length > 0) {
        c_start = document.cookie.indexOf(c_name + "=");
        if (c_start != -1) {
            c_start = c_start + c_name.length + 1;
            c_end = document.cookie.indexOf(";", c_start);
            if (c_end == -1) c_end = document.cookie.length;
            return unescape(document.cookie.substring(c_start, c_end));
        }
    }
    return "";
}

怪癖模式:

function readCokie(name) {
    var nameEQ = name + "=";
    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, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}

他们都寻找;字符来分割cookie,据我所知这总是正确的。 - VoteyDisciple
1
getCookie 函数有误,请考虑一下如果您获取到一个名为 ART= 的 cookie,而您又有一个名为 SMART= 的 cookie,会发生什么。 - Marco Demaio
1
w3schools 自那时改变了他们的 getCookie 函数(参见 http://www.w3schools.com/js/js_cookies.asp),现在可能更好用了。 - pauloya
他们现在正在使用相同的函数 - 只是用不同的名称和空格。 - Kevin Sandow
5个回答

92

W3CSchool的函数是错误的。如果存在多个具有相同后缀的cookie,则该函数会失败,例如:

ffoo=bar; foo=baz

当您搜索foo时,它将返回ffoo的值而不是foo的值。
现在我要做的是:首先,您需要了解cookie如何传输的语法。Netscape的原始规范(只有副本可用,例如haxx.se上的此规范)使用分号来分隔多个cookie,而每个名称/值对具有以下语法:

NAME=VALUE
这个字符串是由字符序列组成的,不包括分号、逗号和空格。如果需要在名称或值中放置这样的数据,则建议使用一些编码方法,例如URL样式的%XX编码,尽管没有定义或要求任何编码。

因此,在分号或逗号处拆分document.cookie字符串是一个可行的选项。
除此之外,RFC 2109也指定cookie由分号或逗号分隔。
cookie          =       "Cookie:" cookie-version
                        1*((";" | ",") cookie-value)
cookie-value    =       NAME "=" VALUE [";" path] [";" domain]
cookie-version  =       "$Version" "=" value
NAME            =       attr
VALUE           =       value
path            =       "$Path" "=" value
domain          =       "$Domain" "=" value

尽管两者都被允许,但逗号是HTTP列表项的默认分隔符,因此建议使用逗号。

注意:为了向后兼容,Cookie头中的分隔符在任何地方都是分号(;)。服务器也应该接受逗号(,)作为cookie值之间的分隔符,以实现未来的兼容性。

此外,名称/值对还有一些限制,因为RFC 2616中指定的值也可以是带引号的字符串:

attr        =     token
value       =     token | quoted-string
所以这两个cookie版本需要分别处理:
if (typeof String.prototype.trimLeft !== "function") {
    String.prototype.trimLeft = function() {
        return this.replace(/^\s+/, "");
    };
}
if (typeof String.prototype.trimRight !== "function") {
    String.prototype.trimRight = function() {
        return this.replace(/\s+$/, "");
    };
}
if (typeof Array.prototype.map !== "function") {
    Array.prototype.map = function(callback, thisArg) {
        for (var i=0, n=this.length, a=[]; i<n; i++) {
            if (i in this) a[i] = callback.call(thisArg, this[i]);
        }
        return a;
    };
}
function getCookies() {
    var c = document.cookie, v = 0, cookies = {};
    if (document.cookie.match(/^\s*\$Version=(?:"1"|1);\s*(.*)/)) {
        c = RegExp.$1;
        v = 1;
    }
    if (v === 0) {
        c.split(/[,;]/).map(function(cookie) {
            var parts = cookie.split(/=/, 2),
                name = decodeURIComponent(parts[0].trimLeft()),
                value = parts.length > 1 ? decodeURIComponent(parts[1].trimRight()) : null;
            cookies[name] = value;
        });
    } else {
        c.match(/(?:^|\s+)([!#$%&'*+\-.0-9A-Z^`a-z|~]+)=([!#$%&'*+\-.0-9A-Z^`a-z|~]*|"(?:[\x20-\x7E\x80\xFF]|\\[\x00-\x7F])*")(?=\s*[,;]|$)/g).map(function($0, $1) {
            var name = $0,
                value = $1.charAt(0) === '"'
                          ? $1.substr(1, -1).replace(/\\(.)/g, "$1")
                          : $1;
            cookies[name] = value;
        });
    }
    return cookies;
}
function getCookie(name) {
    return getCookies()[name];
}

1
我会将 trimRighttrimLeft 移出 getCookie 函数:你只需要设置一次。 - Tim Down
@Marco Demaio:$Version=1 属性是 RFC 规范的一部分,用于识别这些规范的 cookie,并且当浏览器开始实现它们时可能会变得非常重要(尽管我不确定 document.cookie 值将如何反映该属性)。 - Gumbo
11
这个回答是过度设计的一个典型例子。为什么要包含所有那些代码,当问题可以通过创建一个很可能唯一的cookie名称来轻松解决。例如,NAMESPACE_KEY。 - Anthony Martin
1
这个答案似乎无法处理带有“=”作为cookie值的任何内容。将答案的getCookies()函数与document.cookie.split('; ')进行比较。 - user456584
服务器还应该接受逗号(,)作为cookie值之间的分隔符,以便实现未来的兼容性。但是如果“expires”值中包含逗号,这怎么可能呢? - Rag
显示剩余5条评论

12

是的,W3Schools 的解决方案是错误的。

对于那些需要的人,这里有一个更简单的解决方案,它只需在索引处添加一个空格,这样单个调用 indexOf() 就只返回正确的 cookie。

function getCookie(c_name) {
    var c_value = " " + document.cookie;
    var c_start = c_value.indexOf(" " + c_name + "=");
    if (c_start == -1) {
        c_value = null;
    }
    else {
        c_start = c_value.indexOf("=", c_start) + 1;
        var c_end = c_value.indexOf(";", c_start);
        if (c_end == -1) {
            c_end = c_value.length;
        }
        c_value = unescape(c_value.substring(c_start,c_end));
    }
    return c_value;
}

现在应该优先使用 decodeURIComponent 而不是 unescape。详情请参见此处 - BornToCode

6
这段来自w3schools的内容是不正确的,因为它可能会导致获取错误的cookie:
c_start = document.cookie.indexOf(c_name + "=");

如果您正在寻找名为foo的cookie(我们假设这是一个现有的cookie),那么在document.cookie中将会有字符串foo=bar
但是,不能保证不会出现字符串xfoo=something。请注意,这仍然包含子字符串foo=,因此w3schools代码将找到它。如果xfoo cookie恰好排在第一位,您将返回(错误地!)something值,而不是预期的bar
在两个代码之间进行选择时,永远不要选择基本上有问题的代码。

2
上面展示的所有代码都是有问题的。两个常见问题是:(1) 如果一个cookie名是另一个cookie名的后缀,getcookie函数可能会返回错误的值;(2) setcookie函数不保护cookie值,这意味着如果cookie值包含(例如)";",则所有cookie都将损坏且无法解析。
简而言之,请使用这个写得很好的库: https://github.com/js-cookie/js-cookie

1
尝试了接受的答案,用于包含等号的双引号cookie值,但它没有起作用,但是js-cookie完成了工作。 - dlauzon

1
这是我的版本,它涵盖了带引号值的边缘情况。
function getCookies() {
  const REGEXP = /([\w\.]+)\s*=\s*(?:"((?:\\"|[^"])*)"|(.*?))\s*(?:[;,]|$)/g;
  let cookies = {};
  let match;
  while( (match = REGEXP.exec(document.cookie)) !== null ) {
    let value = match[2] || match[3];
    cookies[match[1]] = decodeURIComponent(value);
  }
  return cookies;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接