一 cookie
1.1 cookie的诞生
由于HTTP协议是无状态的,而服务器端的业务必须是要有状态的。Cookie诞生的最初目的是为了存储web中的状态信息,以方便服务器端使用。比如判断用户是否是第一次访问网站。目前最新的规范是RFC 6265,它是一个由浏览器服务器共同协作实现的规范。
1.2 cookie机制
当用户第一次访问并登陆一个网站的时候,cookie的设置以及发送会经历以下4个步骤:
第一次请求写入cookie
下次请求带上Cookie
1.3 cookie的存取
1.3.1 设置cookie:
document.cookie = newCookie;
// document.cookie = "name=value;domain=www.mydomain.com;path=/;expires=Tue, 01 Sep 2020 07:29:10 GMT;HttpOnly;secure";
key=value
键值对(必填)。;path=path
如果没有定义,默认为当前文档位置的路径。”/”表示允许所有路径都可以使用。;domain=domain
生成该 Cookie 的域名如果没有定义,默认为当前文档位置的路径的域名部分。与早期规范相反的是,在域名前面加 . 符将会被忽视,因为浏览器也许会拒绝设置这样的cookie。如果指定了一个域,那么子域也包含在内。;max-age=max-age-in-seconds
;expires=date-in-GMTString-format
如果没有定义,cookie会在对话结束时过期。Date.toUTCString();secure
如果设置了这个属性,那么只会在 SSH 连接时才会回传该 Cookie。
cookie的值字符串可以用encodeURIComponent()来保证它不包含任何逗号、分号或空格(cookie值中禁止使用这些值).
1.3.2 修改cookie
直接设置新的值,旧的值会被覆盖。
1.3.3 删除cookie
只需要设置 expires 参数为以前的时间即可。比如:Thu, 01 Jan 1970 00:00:00 GMT。
document.cookie = "name=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
1.4 cookie框架
作为一个格式化过的字符串,cookie的值有时很难被自然地处理。下面的库的目的是通过定义一个和Storage对象部分一致的对象(docCookies),简化document.cookie 的获取方法。它提供完全的Unicode支持。
/*\
|*|
|*| :: cookies.js ::
|*|
|*| A complete cookies reader/writer framework with full unicode support.
|*|
|*| https://developer.mozilla.org/en-US/docs/DOM/document.cookie
|*|
|*| This framework is released under the GNU Public License, version 3 or later.
|*| http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*| Syntaxes:
|*|
|*| * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
|*| * docCookies.getItem(name)
|*| * docCookies.removeItem(name[, path], domain)
|*| * docCookies.hasItem(name)
|*| * docCookies.keys()
|*|
\*/
var docCookies = {
getItem: function (sKey) {
return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
},
setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
var sExpires = "";
if (vEnd) {
switch (vEnd.constructor) {
case Number:
sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
break;
case String:
sExpires = "; expires=" + vEnd;
break;
case Date:
sExpires = "; expires=" + vEnd.toUTCString();
break;
}
}
document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
return true;
},
removeItem: function (sKey, sPath, sDomain) {
if (!sKey || !this.hasItem(sKey)) { return false; }
document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
return true;
},
hasItem: function (sKey) {
return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[-.+*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
},
keys: /* optional method: you can safely remove it! */ function () {
var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
return aKeys;
}
};
示例用法:
docCookies.setItem("test0", "Hello world!");
docCookies.setItem("test1", "Unicode test: \u00E0\u00E8\u00EC\u00F2\u00F9", Infinity);
docCookies.setItem("test2", "Hello world!", new Date(2020, 5, 12));
docCookies.setItem("test3", "Hello world!", new Date(2027, 2, 3), "/blog");
docCookies.setItem("test4", "Hello world!", "Sun, 06 Nov 2022 21:43:15 GMT");
docCookies.setItem("test5", "Hello world!", "Tue, 06 Dec 2022 13:11:07 GMT", "/home");
docCookies.setItem("test6", "Hello world!", 150);
docCookies.setItem("test7", "Hello world!", 245, "/content");
docCookies.setItem("test8", "Hello world!", null, null, "example.com");
docCookies.setItem("test9", "Hello world!", null, null, null, true);
docCookies.setItem("test1;=", "Safe character test;=", Infinity);
alert(docCookies.keys().join("\n"));
alert(docCookies.getItem("test1"));
alert(docCookies.getItem("test5"));
docCookies.removeItem("test1");
docCookies.removeItem("test5", "/home");
alert(docCookies.getItem("test1"));
alert(docCookies.getItem("test5"));
alert(docCookies.getItem("unexistingCookie"));
alert(docCookies.getItem());
alert(docCookies.getItem("test1;="));
1.5 session以及cookie和session的关系
session
Session保存在服务器端,为了获取更高的存取速度,服务器一般会把Session放在内存里面,每个用户都会有一个独立的Session。如果Session里面的内容太过复杂,当大量的用户访问服务器时,可能会导致内存溢出,所以我们的session内容应当适当的精简。
当我们第一次访问服务器时,服务器会给我们自动创建一个Session,生成session后,只要用户继续访问,服务器就会更新session的最后访问时间,并且维护这个session。当用户访问服务器一次,无论是否读写了session,服务器都会认定这个session活跃(active)了一次。当越来越多的用户访问我们的服务器时,因此我们的session会越来越多。为了防止内存溢出,服务器会把长时间没有活跃的Session删除。这个时间就是session的超时时间,过了超时时间,我们的session就会自动失效。
http是无状态的协议,客户每次读取web页面时,服务器都打开新的会话,而且服务器也不会自动维护客户的上下文信息,那么要怎么才能实现网上商店中的购物车呢,session就是一种保存上下文信息的机制,它是针对每一个用户的,变量的值保存在服务器端,session是以cookie或URL重写为基础的,默认使用cookie来实现,服务器向客户端浏览器发送一个名为JSESSIONID的Cookie而JSESSIONID的值是SessionID,session就是用SessionID区分不同的客户身份的。
cookie和session的关系
二 localStorage
只读的localStorage
属性允许你访问一个Document
源(origin)的对象 Storage
;存储的数据将保存在浏览器会话中。localStorage
类似 sessionStorage
,但其区别在于:存储在 localStorage
的数据可以长期保留;而当页面会话结束——也就是说,当页面被关闭时,存储在 sessionStorage
的数据会被清除 。
localStorage是区分协议存储的,http://example.com/和https://example.com/中的localStorage是独立的。
2.1 localStorage使用
localStorage.setItem('myCat', 'Tom');
let cat = localStorage.getItem('myCat');
localStorage.removeItem('myCat');
// 移除所有
localStorage.clear();
三 sessionStorage
sessionStorage
属性允许你访问一个,对应当前源的 session Storage
对象。它与 localStorage
相似,不同之处在于 localStorage
里面存储的数据没有过期时间设置,而存储在 sessionStorage
里面的数据在页面会话结束时会被清除。
- 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的页面会话。
- 在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,这点和 session cookies 的运行方式不同。
- 打开多个相同的URL的Tabs页面,会创建各自的
sessionStorage
。 - 关闭对应浏览器tab,会清除对应的
sessionStorage
。
sessionStorage
也是区分协议存储的,example.com/和example.com/中的sessionStorage
是独立的。
3.1 sessionStorage使用
// 保存数据到 sessionStorage
sessionStorage.setItem('key', 'value');
// 从 sessionStorage 获取数据
let data = sessionStorage.getItem('key');
// 从 sessionStorage 删除保存的数据
sessionStorage.removeItem('key');
// 从 sessionStorage 删除所有保存的数据
sessionStorage.clear();
四 IndexDB
IndexDB 是一个运行在浏览器上的事务型数据库系统,用于在客户端存储大量的结构化数据(也包括文件/二进制大型对象(blobs))。理论上来说,IndexDB 是没有存储上限的(一般来说不会小于 250M)。
Note: 此特性在 Web Worker 中可用。
4.1 IndexDB特点
- 键值对储存 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以”键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- 异步 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
- 支持事务 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
- 同源限制 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
- 储存空间大 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
- 支持二进制储存 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
4.2 IndexDB使用
1 打开/创建一个 IndexDB 数据库(当该数据库不存在时,open 方法会直接创建一个新数据库)。
let db
// 参数1位数据库名,参数2为版本号
const request = window.indexedDB.open("exampleDB", 1)
request.onerror = function(event) {
console.log('无法使用IndexDB')
}
request.onsuccess = function(event){
db = event.target.result
console.log("打开了IndexDB")
}
2 创建一个 object store(可以理解为数据库中的“表”)。
// onupgradeneeded事件会在初始化数据库/版本发生更新时被调用
request.onupgradeneeded = function(event){
let objectStore
if (!db.objectStoreNames.contains('test')) {
objectStore = db.createObjectStore('test', { keyPath: 'id' })
}
}
3 构建一个事务来执行一些数据库操作,像增加或提取数据等。
// 创建事务,指定表格名称和读写权限
const transaction = db.transaction(["test"],"readwrite")
// 拿到Object Store对象
const objectStore = transaction.objectStore("test")
// 向表格写入数据
objectStore.add({id: 1, name: 'name1'})
4 通过监听正确类型的事件以等待操作完成。
transaction.oncomplete = function(event) {
console.log("操作成功", event)
}
transaction.onerror = function(event) {
console.log("操作失败", event)
}
比较
分类
生命周期
存储容量
存储位置
cookie
默认保存在内存中,随浏览器关闭失效(如果设置过期时间,在到过期时间后失效)
4KB
保存在客户端,每次请求时都会带上
localStorage
理论上永久有效的,除非主动清除。
4.98MB(不同浏览器情况不同,safari 2.49M)
保存在客户端,不与服务端交互。节省网络流量
sessionStorage
仅在当前网页会话下有效,关闭页面或浏览器后会被清除。
4.98MB(部分浏览器没有限制)
同上
IndexDB
理论上永久有效的,除非主动清除。
基本无限制
同上
总结:
因为每次http请求都会携带cookie信息,这样无形中浪费了带宽,而且容量限制较大,所以cookie应该尽可能少的使用。localStorage和sessionStorage就看是否需要永久储存。
兼容性
cookie基本都支持。
localStorage和sessionStorage的浏览器支持情况:
检测是否支持的方法:
// type='localStorage'或者 type='sessionStorage'
function storageAvailable(type) {
var storage;
try {
storage = window[type];
var x = '__storage_test__';
storage.setItem(x, x);
storage.removeItem(x);
return true;
}
catch(e) {
return e instanceof DOMException && (
// everything except Firefox
e.code === 22 ||
// Firefox
e.code === 1014 ||
// test name field too, because code might not be present
// everything except Firefox
e.name === 'QuotaExceededError' ||
// Firefox
e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
// acknowledge QuotaExceededError only if there's something already stored
(storage && storage.length !== 0);
}
}
参考资料
- [1] MDN-document.cookie
- [2] MDN-Window.localStorage
- [3] MDN-window.sessionStorage
- [4] MDN-使用Web Storage API
- [5] 掘金-快狗打车前端团队-localStorage、sessionStorage、cookie、session几种web数据存储方式对比总结
- [6] 浏览器数据库 IndexedDB 入门教程 – 阮一峰的网络日志
- [7] 掘金-本地存储—— Cookie 到 Web Storage、IndexDB
- [8] 知乎-阿里面试题cookie和session的区别及session的生命周期
- [9] 知乎-Cookie和session应该这样理解
作者:chunjine
链接:https://juejin.im/post/6893858445485670413
看完两件小事
如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:
- 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
- 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程
本文著作权归作者所有,如若转载,请注明出处
转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com