最近做了一个需要在不同二级域名的两个项目之间进行通知,尝试了 iframe 方式,发现在移动端失败,于是打算通过 cookie 来进行消息通知。所以去找了前端操作 cookie 的库,后来找到 js-cookie 这个库。由于之前操作 cookie 的经历有限,所以特意去阅读了源码并总结。

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*!
 * JavaScript Cookie v2.2.0
 * https://github.com/js-cookie/js-cookie
 *
 * Copyright 2006, 2015 Klaus Hartl & Fagner Brack
 * Released under the MIT license
 */
;(function (factory) {
  // 各种方式的模块定义
	var registeredInModuleLoader = false;
	if (typeof define === 'function' && define.amd) {
		define(factory);
		registeredInModuleLoader = true;
	}
	if (typeof exports === 'object') {
		module.exports = factory();
		registeredInModuleLoader = true;
	}
	if (!registeredInModuleLoader) {
		var OldCookies = window.Cookies;
		var api = window.Cookies = factory();
		api.noConflict = function () {
			window.Cookies = OldCookies;
			return api;
		};
	}
}(function () {
  // 对象拼接方法
	function extend () {
		var i = 0;
		var result = {};
		for (; i < arguments.length; i++) {
			var attributes = arguments[ i ];
			for (var key in attributes) {
				result[key] = attributes[key];
			}
		}
		return result;
	}

	function init (converter) {
		function api (key, value, attributes) {
			var result;
			if (typeof document === 'undefined') {
				return;
			}

			// Write 当参数大于 1 的时候认为是 write 操作
			if (arguments.length > 1) {
        // 拼接参数及默认参数
				attributes = extend({
					path: '/'
				}, api.defaults, attributes);
        // 设置过期时间, attributes.expires 是一个数字,代表过期天数
				if (typeof attributes.expires === 'number') {
          var expires = new Date();
          // 获取当前时间,在加上 attributes.expires 的天数 864e+5 是一天的毫秒数
					expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
					attributes.expires = expires;
				}

				// We're using "expires" because "max-age" is not supported by IE,IE不支持 max-age,所以使用 expires 来设置过期时间
				attributes.expires = attributes.expires ? attributes.expires.toUTCString() : '';

				try {
          result = JSON.stringify(value);
          // 如果是对象或者数组,转为 JSON
					if (/^[\{\[]/.test(result)) {
						value = result;
					}
				} catch (e) {}

				if (!converter.write) {
          // 对 value 值进行了 encode 同时对其中部分 code 做了 decode 注意 replace 方法用的很高级
					value = encodeURIComponent(String(value))
						.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
				} else {
					value = converter.write(value, key);
				}
        // 对 key 值做了处理
				key = encodeURIComponent(String(key));
				key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
				key = key.replace(/[\(\)]/g, escape);

				var stringifiedAttributes = '';
        // 将传入的 attributes 连接起来 domain=.geekang.org; expries=121314212; http
				for (var attributeName in attributes) {
					if (!attributes[attributeName]) {
						continue;
					}
					stringifiedAttributes += '; ' + attributeName;
					if (attributes[attributeName] === true) {
						continue;
					}
					stringifiedAttributes += '=' + attributes[attributeName];
        }
        // 写入 cookie。这里说明 cookie 写入的方式 key=value; domain=.geekbang.org...
        console.log(key + '=' + value + stringifiedAttributes)
				return (document.cookie = key + '=' + value + stringifiedAttributes);
			}

			// Read

			if (!key) {
				result = {};
			}

			// To prevent the for loop in the first place assign an empty array
			// in case there are no cookies at all. Also prevents odd result when
			// calling "get()"
			var cookies = document.cookie ? document.cookie.split('; ') : [];
			var rdecode = /(%[0-9A-Z]{2})+/g;
			var i = 0;

			for (; i < cookies.length; i++) {
				var parts = cookies[i].split('=');
				var cookie = parts.slice(1).join('=');

				if (!this.json && cookie.charAt(0) === '"') {
					cookie = cookie.slice(1, -1);
				}

				try {
					var name = parts[0].replace(rdecode, decodeURIComponent);
					cookie = converter.read ?
						converter.read(cookie, name) : converter(cookie, name) ||
						cookie.replace(rdecode, decodeURIComponent);

					if (this.json) {
						try {
							cookie = JSON.parse(cookie);
						} catch (e) {}
					}

					if (key === name) {
						result = cookie;
						break;
					}

					if (!key) {
						result[name] = cookie;
					}
				} catch (e) {}
			}

			return result;
		}
		api.set = api;
		api.get = function (key) {
			return api.call(api, key);
		};
		api.getJSON = function () {
			return api.apply({
				json: true
			}, [].slice.call(arguments));
		};
		api.defaults = {};
    // remove 直接设置了传入的 key 的 value 位 '',并且设置过期时间为负
		api.remove = function (key, attributes) {
			api(key, '', extend(attributes, {
				expires: -1
			}));
		};

		api.withConverter = init;

		return api;
	}

	return init(function () {});
}));

结论如下:

  1. cookie 的读取方式为 document.cookie。获取到的是所有该域下 cookie key value 的字符串,不同 key value 之间以 ; 隔开。例如 text=1; name=newming; age=25。注意拿不到 expires path 等信息。然后可以通过字符串分割拿到某一个信息
  2. cookie 的写入方式为 document.cookie = ‘key=value; expires=time; path=/; ‘。设置的时候为一条一条的设置,可以带上 path domain 等信息,属性之间同样以 ; 隔开,如果 value 是对象的活需要转为字符串
  3. cookie 的删除 docoment.cookie = 'key=; path=/; expires=time' 。注意时间为 UTCString,并且小于当前时间,即直接将某条属性设置过期即可删除