We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
作者前段时间在做类似Google Analytics(以下简称GA)的第三方监控脚本。所以对GA的前端代码做过调研,对GA的压缩后代码做了一定程度上的人肉美化。这里美化的是analytics.js的j41版本,本文提到的小技巧也是基于这个版本的js。
cookie的本质是存储在浏览器端的一段简单数据(多个键值对),浏览器会从服务器接受或者发送给服务器cookie。这样便可以为没有状态的HTTP协议提供了记录状态信息的方法,知道多个不同的HTTP请求是否来自同一个浏览器。
在浏览器中cookie是支持范围最广的存储数据的手段了,前端工程师一般都曾经或多或少的使用过cookie来存储数据或变量。浏览器提供了document.cookie这个接口来增删改查cookie。但是只能一次设置一个cookie。使用方法如下:
document.cookie
// 设置名为key的cookie,值为value document.cookie = 'key=value';
代码中省略了其他可选配置,具体使用方法可以参考MDN文档。通过domain和path参数,可以将cookie设置在不同的域名或者路径下,例如:
domain
path
// 设置名为key的cookie,值为value document.cookie = 'A=1;path=/;domain=map.baidu.com;';
上面的代码在域名map.baidu.com下的根路径(/)设置了cookie A的值为1。我们可以通过document.cookie来获取当前域名和路径下的所有cookie。当我们访问http://map.baidu.com页面时,执行document.cookie获得的结果是A=1。
map.baidu.com
/
1
http://map.baidu.com
A=1
大家知道域名是有父域名和子域名的区别的,键名相同的两个cookie可以分别设置在父子域名上。例如如下代码:
document.cookie = 'A=1;path=/;domain=.example.com;'; document.cookie = 'A=2;path=/;domain=a.example.com;';
顺带提到一个冷门的知识点:以.开头的域名表示cookie设置在此域名及其子域上,否则不适用于其子域名。不过从RFC 2965标准开始浏览器便会自动为domain属性值自动添加一个.前缀,所以在设置domain时加不加.前缀已经没有区别了。但是为了兼容一些旧浏览还是加.为好。另外如果不设置domain浏览器会默认用当前页面的域名,这时浏览器不会自动添加.前缀,自然也就不会包含子域了。
.
在.example.com和a.example.com域名下面分别设置了A=1和A=2两个cookie。此时我们在http://a.example.com页面下执行document.cookie得到的结果是:A=1;A=2。如下图:
.example.com
a.example.com
A=2
http://a.example.com
A=1;A=2
开发者是没办法从结果中知道这个cookie是设置在哪个域名上的。同样这个情况也适用于不同的父子路径上。
这在一般情况下对开发者不会有影响,但是对于GA来说确实致命的。GA会在当前网站域名下面设置一个全局唯一的cookie _ga,用于标志相同的用户。现在大型的公司都会分不同的网站域名,例如:baidu.com和ditu.baidu.com。假设百度使用了GA~ 那么GA会分别在两个域名下设置不同的_ga cookie值,这样在baidu.com下GA便会拿到两个_ga值。不知道该用哪个,傻傻分不清楚。
_ga
baidu.com
ditu.baidu.com
为了解决这个问题,GA在cookie的值上面做文章。可以看到冲突只会发生在父子域名和父子路径上。因为cookie本身的特殊性:所有http请求会带着该域名下的所有cookie。如果cookie值太长太多会消耗太多带宽。GA通过计算域名和路径的“长度”来唯一标示这个cookie所设置在的域名和路径。计算“长度”的方法如下:
/** * normalize domain. * remove the first '.' if exist. * @param {string} domain domain String * @return {string} normalized domain */ var normalizeDomain = function (domain) { return domain.indexOf('.') === 0 ? domain.substr(1) : domain; }; /** * get count of domain. * getDomainCount('qq.com') === 2 * getDomainCount('.qq.com') === 2 * getDomainCount('b.qq.com') === 3 * getDomainCount('e.qidian.qq.com') === 4 * * @param {string} domain domain String * @return {string} normalized domain */ var getDomainCount = function (domain) { return normalizeDomain(domain).split('.').length; }; /** * normalize path * normalizePath('') === '/' * normalizePath('/') === '/' * normalizePath('/ping') === '/ping' * normalizePath('/ping/pv') === '/ping/pv' * normalizePath('ping/pv') === '/ping/pv' * normalizePath('ping/pv/') === '/ping/pv' * * @param {string} path path * @return {string} path */ var normalizePath = function (path) { if (!path) { return '/'; } if (path.length > 1 && path.lastIndexOf('/') === path.length - 1) { // remove last '/' path = path.substr(0, path.length - 1); } if (path.indexOf('/') !== 0) { // fill up first '/' path = '/' + path; } return path; }; /** * get count of path * getPathCount('') === 1 * getPathCount('/') === 1 * getPathCount('/ping') === 2 * getPathCount('/ping/pv') === 3 * getPathCount('ping/pv') === 3 * getPathCount('ping/pv/') === 3 * * @param {string} path path * @return {number} count of path */ var getPathCount = function (path) { path = normalizePath(path); return path === '/' ? 1 : path.split('/').length; }; /** * Get domain and path count string. * domainCount + '-' + pathCount + '$' * * @param {Window=} win window context. * @param {string=} domain cookie domain. * @param {string=} path cookie path. * @return {string} count string. */ var getDomainAndPathCount = function (win, domain, path) { win = win || window; var location = win.location; var pathCount = getPathCount(path != null ? path : location.pathname); var domainCount = getDomainCount(domain != null ? domain : location.hostname); return domainCount + (pathCount > 1 ? '-' + pathCount : '') + '-'; };
GA在设置cookie时加上domain和path的长度前缀,然后在取出cookie时遍历所有的名字相同的cookie找到指定域名和路径下的cookie。示例代码如下:
/** * Set cookie. * @param {string} key cookie name. * @param {string|number} value cookie value. * @param {Window=} win window context. * @param {number=} expires cookie expired time in milliseconds. * @param {string=} domain cookie domain. * @param {string=} path cookie path. * @return {boolean} success or not. */ var setCookie = function (key, value, win, expires, domain, path) { var domainAndPathCount = getDomainAndPathCount(win, domain, path); value = value + ''; return setCookieRaw(key, domainAndPathCount + value.replace(/\-/g, '%2d'), win, expires, domain, path); }; /** * Get cookie value. * * @param {string} key key name. * @param {Window=} win window context. * @param {string=} domain cookie domain. * @param {string=} path cookie path. * @return {string} cookie value. */ var getCookie = function (key, win, domain, path) { var results = getCookieRaw(key, win); var domainAndPathCount = getDomainAndPathCount(win, domain, path); for (var i = 0; i < results.length; i++) { var r = results[i]; if (r.indexOf(domainAndPathCount) === 0) { return r.slice(domainAndPathCount.length).replace(/%2d/g, '-'); } } return ''; };
魔鬼👹藏于细节。GA对细节的追求并没有止步于此。cookie名_ga虽然一般很少有开发者会用到,但是不怕一万就怕万一。如果cookie名_ga冲突了并被改写成了别的值,那么GA很可能会发送一个不符合要求的值过去。这对服务器端解析也非常不利。
GA在cookie的值之前又加了前缀GA1.,下次使用这个cookie时都会检查是否带有GA1.前缀。如果不存在前缀则直接覆盖生成新的,如果存在则继续复用原cookie值。
GA1.
除了防止_ga被开发者覆盖之外,猜想还有一个作用,就是“版本号”。多个GA版本之间如果_ga的cookie值生成算法不一样需要特殊处理,那么可以依据GA1.前缀来判断应该是哪个版本,采取正确的操作。
总结下_ga cookie值的格式如下:
// GA{version}.{domainCount}[-{pathCount}].{randomNumber}.{time} // path是'/'时 document.cookie = '_ga=GA1.3.494346849.1446193077'; // 没有path时 document.cookie = '_ga=GA1.3-2.494346849.1446193077';
细微之处见真章:+1:
参考文档:
The text was updated successfully, but these errors were encountered:
No branches or pull requests
cookie的本质是存储在浏览器端的一段简单数据(多个键值对),浏览器会从服务器接受或者发送给服务器cookie。这样便可以为没有状态的HTTP协议提供了记录状态信息的方法,知道多个不同的HTTP请求是否来自同一个浏览器。
在浏览器中cookie是支持范围最广的存储数据的手段了,前端工程师一般都曾经或多或少的使用过cookie来存储数据或变量。浏览器提供了
document.cookie
这个接口来增删改查cookie。但是只能一次设置一个cookie。使用方法如下:代码中省略了其他可选配置,具体使用方法可以参考MDN文档。通过
domain
和path
参数,可以将cookie设置在不同的域名或者路径下,例如:上面的代码在域名
map.baidu.com
下的根路径(/
)设置了cookie A的值为1
。我们可以通过document.cookie
来获取当前域名和路径下的所有cookie。当我们访问http://map.baidu.com
页面时,执行document.cookie
获得的结果是A=1
。大家知道域名是有父域名和子域名的区别的,键名相同的两个cookie可以分别设置在父子域名上。例如如下代码:
在
.example.com
和a.example.com
域名下面分别设置了A=1
和A=2
两个cookie。此时我们在http://a.example.com
页面下执行document.cookie
得到的结果是:A=1;A=2
。如下图:开发者是没办法从结果中知道这个cookie是设置在哪个域名上的。同样这个情况也适用于不同的父子路径上。
这在一般情况下对开发者不会有影响,但是对于GA来说确实致命的。GA会在当前网站域名下面设置一个全局唯一的cookie
_ga
,用于标志相同的用户。现在大型的公司都会分不同的网站域名,例如:baidu.com
和ditu.baidu.com
。假设百度使用了GA~ 那么GA会分别在两个域名下设置不同的_ga
cookie值,这样在baidu.com
下GA便会拿到两个_ga
值。不知道该用哪个,傻傻分不清楚。为了解决这个问题,GA在cookie的值上面做文章。可以看到冲突只会发生在父子域名和父子路径上。因为cookie本身的特殊性:所有http请求会带着该域名下的所有cookie。如果cookie值太长太多会消耗太多带宽。GA通过计算域名和路径的“长度”来唯一标示这个cookie所设置在的域名和路径。计算“长度”的方法如下:
GA在设置cookie时加上
domain
和path
的长度前缀,然后在取出cookie时遍历所有的名字相同的cookie找到指定域名和路径下的cookie。示例代码如下:魔鬼👹藏于细节。GA对细节的追求并没有止步于此。cookie名
_ga
虽然一般很少有开发者会用到,但是不怕一万就怕万一。如果cookie名_ga
冲突了并被改写成了别的值,那么GA很可能会发送一个不符合要求的值过去。这对服务器端解析也非常不利。GA在cookie的值之前又加了前缀
GA1.
,下次使用这个cookie时都会检查是否带有GA1.
前缀。如果不存在前缀则直接覆盖生成新的,如果存在则继续复用原cookie值。除了防止
_ga
被开发者覆盖之外,猜想还有一个作用,就是“版本号”。多个GA版本之间如果_ga
的cookie值生成算法不一样需要特殊处理,那么可以依据GA1.
前缀来判断应该是哪个版本,采取正确的操作。总结下
_ga
cookie值的格式如下:参考文档:
The text was updated successfully, but these errors were encountered: