说明
随着 Web 应用程序的出现,直接在客户端存储用户信息的需求也随之出现。
这背后的想法是合理的:与特定用户相关的信息应该保存在用户的机器上。
无论是登录信息、个人偏好,还是其他数据,Web 应用程序提供者都需要有办法把它们保存在客户端。
对该问题的第一个解决方案就是 cookie,今天,cookie 只是在客户端存储数据的一个选项。
各种方式对比
cookie | sessionStorage | localStorage | IndexedDB | session | |
---|---|---|---|---|---|
共同点 | 1、通过key value储存 2、同域名可用 |
||||
储存位置 | 客户端 | 服务端 | |||
特点 | 随请求头每次提交 | 不随请求头提交,页面关闭后消失 | 不随请求头提交,可长期储存 | 安全 | |
跨页/跨域 | 可跨页 不可跨域 |
不跨页 不可跨域 |
可跨页 不可跨域 |
=======================
cookie
说明
通常是服务器设置的,服务器发送给我们的响应里面,有个set cookie属性,浏览器发现有这个属性,就会帮我设置这些数据到浏览器中。
当发送请求的时候,浏览器默认会把cookie的内容加到请求中,发送给服务器,服务器就可以验证cookie的内容,从而判断发送的人是谁,或者发送的数据是否有效等等。
作用
原理示例
1、客户端发送请求(如登录等)
2、服务器验证信息,然后发回响应,响应里面就携带cookie,里面设置一些自定义的值
3、后续发送的请求都可以携带服务器发过来的cookie值,告诉服务器我是哪位
服务器创建cookie
类型
构成属性
名称 Name
唯一标识 cookie 的名称。cookie 名不区分大小写,因此 myCookie 和 MyCookie 是同一个名称。
不过,实践中最好将 cookie 名当成区分大小写来对待,因为一些服务器软件可能这样对待它们。cookie 名必须经过 URL 编码。
值 Value
存储在 cookie 里的字符串值。这个值必须经过 URL 编码。
作用域 Domain
过期时间 Expires
//Expires=就是设置过期时间
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT;
表示何时删除 cookie 的时间戳(即什么时间之后就不发送到服务器了)。
默认情况下,浏览器会话结束后会删除所有 cookie。
不过,也可以设置删除 cookie 的时间。
这个值是 GMT 格式(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定删除 cookie 的具体时间。
这样即使关闭浏览器 cookie 也会保留在用户机器上。
把过期时间设置为过去的时间会立即删除 cookie。
secure / HttpOnly 属性
还有一种叫作 HTTP-only 的 cookie。
HTTP-only 可以在浏览器设置,也可以在服务器设置,但只能在服务器上读取,这是因为 JavaScript 无法取得这种 cookie 的值。
Set-Cookie: name=value; domain=.wrox.com; path=/; secure
JS操作cookie
在 JavaScript 中处理 cookie 比较麻烦,因为接口过于简单,只有 BOM 的 document.cookie 属性。
获取
获取cookie的值不是很直观,因为直接获取的就是所有的cookie信息,需要自己弄个函数操作
console.log(document.cookie)
// 如,会得到这样的格式:"PSTM=1597907860; BD_UPN=12314753; BIDUPSID=DC5EB694E14FC353969330D1A6DD2556;"
所有名和值都是 URL 编码的,因此必须使用 decodeURIComponent()解码
设置
在设置值时,可以通过 document.cookie 属性设置新的 cookie 字符串。
这个字符串在被解析后会添加到原有 cookie 中。
设置 document.cookie 不会覆盖之前存在的任何 cookie,除非设置了已有的cookie。
document.cookie = "name=Nicholas";
// 虽然这样直接设置也可以,因为不需要在名称或值中编码任何字符,但最好还是使用 encodeURIComponent()对名称和值进行编码
document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas");
删除
无法直接删除,只能重新设置max-age过期时间,让它直接过期
Js-cookie
第三方库,很方便的操作cookie
https://www.yuque.com/yejielin/mypn47/vgmuig#NnaU3
cookie 安全和局限
1、浪费流量
因为所有 cookie 都会作为请求头部由浏览器发送给服务器,所以在 cookie 中保存大量信息可能会影响特定域浏览器请求的性能。
保存的 cookie 越大,请求完成的时间就越长。即使浏览器对 cookie 大小有限制,最好还是尽可能只通过 cookie 保存必要信息,以避免性能问题。
因此cookie 逐渐被淘汰,被Web Storage 和 IndexedDB取代(见下)。
2、不安全
不要在 cookie 中存储重要或敏感的信息,因为都是明文传输,不会加密,除非你自己手动加密。
cookie 数据不是保存在安全的环境中,因此任何人都可能获得。
应该避免把信用卡号或个人地址等信息保存在 cookie 中。
3、大小限制
每次传输的大小大概是4kb,不大
4、客户端限制
目前浏览器是支持cookie,但是在其他程序不一定能支持,比如App、小程序等等。
=======================
Web Storage
说明
这个规范中的草案最终成为了 HTML5 的一部分,后来又独立成为自己的规范。
Web Storage 的目的是解决通过浏览器客户端存储信息,不需要频繁发送回服务器的数据时使用 cookie 的问题。
因为cookie每次都会跟着请求头提交给服务器,影响性能且没必要。
主要有两个目标:
1、提供在 cookie 之外的存储会话数据的途径;
2、提供跨会话持久化存储大量数据的机制。
两个对象:localStorage 和 sessionStorage。
localStorage 是永久存储机制,
sessionStorage 是跨会话的存储机制,关闭浏览器就消失(准确来说是关闭浏览器标签)。
只能存储字符串。非字符串数据在存储之前会自动转换为字符串,获取时也是字符串。
sessionStorage 对象
sessionStorage 对象只存储会话数据,浏览器关闭后消失。
存储在 sessionStorage 中的数据不受页面刷新影响,可以在浏览器崩溃并重启后恢复。
因为 sessionStorage 对象与服务器会话紧密相关,所以在运行本地文件时不能使用。
存储在sessionStorage 对象中的数据只能由最初存储数据的页面使用,跨标签页无法共用。
localStorage 对象
在修订的 HTML5 规范里,localStorage 对象可以浏览器长期存储数据,不会因为关闭浏览器消失。
要访问同一个 localStorage 对象,页面必须来自同一个域(子域不可以)、在相同的端口上使用相同的协议,可以跨页面共享数据。协议、ip、端口,3个相同的情况下才能访问
通用操作
sessionStorage 对象 和 localStorage 对象,都有的方法:
// ================== 1、储存 ==================
// 使用方法存储数据
localStorage.setItem("name", "Nicholas");
// 使用属性存储数据
localStorage.book = "Professional JavaScript";
// ================== 2、获取 ==================
// 使用方法取得数据
let name = localStorage.getItem("name");
// 使用属性取得数据
let book = localStorage.book;
// 根据索引获取
let name1 = localStorage.key(0)
// ================== 3、遍历 ==================
// 可以通过length 属性和 key(索引)方法遍历所有的值,也可以通过for in方法
for(let i=0;i<localStorage.length;i++){
console.log(localStorage.key(i))
}
// ================== 4、删除 ==================
// 使用 delete 删除值
delete sessionStorage.name;
// 使用方法删除值
sessionStorage.removeItem("name");
// 清空所有
sessionStorage.clear();
调试查看
在浏览器中,可以按F12(谷歌浏览器)打开DevTools,在Application这个标签里面可以看到。
DOM储存事件 storage
每当 Storage 对象发生变化时,都会在文档上触发 storage 事件。
使用属性或 setItem()设置值、使用 delete 或 removeItem()删除值,以及每次调用 clear()时都会触发这个事件。
这个事件的事件对象有如下 4 个属性。
domain:存储变化对应的域。
key:被设置或删除的键。
newValue:键被设置的新值,若键被删除则为 null。
oldValue:键变化之前的值。
window.addEventListener("storage", (event) => alert('Storage changed for ${event.domain}'));
限制
与其他客户端数据存储方案一样,Web Storage 也有限制。
具体的限制取决于特定的浏览器。一般来说,客户端数据的大小限制是按照每个源(协议、域和端口)来设置的,因此每个源有固定大小的数据存储空间。
不同浏览器给 localStorage 和 sessionStorage 设置了不同的空间限制,但大多数会限制为每个源 5MB。
只能储存字符串类型的数据,其他的会转换成字符串 (包括JSON)
=======================
IndexedDB
说明
IndexedDB 用于代替目前已废弃的 Web SQL Database API。
优势
1、性能
IndexedDB 背后的思想是创造一套 API,方便 JavaScript 对象的存储和获取,同时也支持查询和搜索,性能比 sessionStorage 和 localforage 更好。
2、异步
IndexedDB 几乎完全是异步的,大多数操作以请求的形式执行,这些请求会异步执行,产生成功的结果或错误。
3、事务操作
事务:对数据库操作的一个操作单元
比如银行转账,A转100元给B,那么:
1、A的账户-100元
2、B的账户+100元
以上2条一起合并为1个操作单元,一起执行。如果其中一个操作失败了,那么全失败,全部回退到执行前。
4、储存空间
储存空间很大,不像sessionStorage 和 localforage 大概只有5M的储存空间。
而且不止可以储存字符串,可以储存对象、二进制数据(图片、文件等)
操作
1、数据库建立连接
let db,
request,
version = 1;
// 1、打开数据库,参数为数据库名称,和版本(整数)。
// 如果给定名称的数据库已存在,则会发送一个打开它的请求;
// 如果不存在,则会发送创建并打开这个数据库的请求。
request = indexedDB.open("admin", version);
// event.target 和 request 指向同一个对象
2、错误事件和成功事件
定义数据库打开的2个事件,一个打开成功,一个打开失败
// 2、要定义 onerror 方法,如果出错则调用这个方法
request.onerror = (event) => alert(`Failed to open: ${event.target.errorCode}`); // event.target.errorCode为错误码
// 2、要定义 onsuccess 方法,成功打开数据库后调用,成功后取得数据库实例
request.onsuccess = (event) => { db = event.target.result}; // event.target.result 数据库(IDBDatabase)实例
// event.target 和 request 指向同一个对象
3、首次运行/版本更新事件
数据库第一次打开,或者版本version有变化,就会调用 onupgradeneeded 方法,见下
4、数据表
// 3、数据库第一次打开,或者版本version有变化,就会调用这个方法
request.onupgradeneeded = (event) => {
// 下面是例子,表示打开数据库后,检查名为users的表是否存在,存在就删除,不存在就创建
// 例子里的几个objectStore属性和方法,只能在onupgradeneeded这个事件里面运行
// objectStoreNames是列出数据库里所有的数据表,contains是是否包含某个字符串
if (db.objectStoreNames.contains("users")) {
// 4、deleteObjectStore 是删除数据表
db.deleteObjectStore("users");
}
// 4、createObjectStore 创建数据表,
// 第一个参数是数据表名
// 第二个参数是一个option对象,
// 对象的属性keyPath是我们自己定义的key的名称
// 对象的属性autoIncrement 表示这个key是否可以自动增加,默认是false
db.createObjectStore("users", { keyPath: "id" });
};
5、事务(准备操作数据)
创建了对象存储之后,剩下的所有操作都是通过事务完成的。
事务要通过调用数据库对象的transaction()方法创建。任何时候,只要想要读取或修改数据,都要通过事务把所有修改操作组织起来。
第一个参数,是要操作的数据表,默认是所有数据表
// 如果不指定参数,则对数据库中所有的对象存储有只读权限。
let transaction = db.transaction();
// 只访问数据表 users 对象存储的信息
let transaction = db.transaction(["users", "anotherStore"]);
// 同时访问多个数据表
let transaction = db.transaction(["users", "anotherStore"]);
第二个参数,是操作的权限,”readonly”(只读)、”readwrite”(读和写)或”versionchange”(变更版本)。
// 可以读和写数据表users
let transaction = db.transaction("users", "readwrite");
// 选择一个数据表
let store = transaction.objectStore("users")
6、操作数据
对数据进行增删改查。查会比较麻烦。
// 2、要定义 onsuccess 方法,成功打开数据库后调用,成功后取得数据库实例
request.onsuccess = (event) => {
db = event.target.result
// 5、
let transaction = db.transaction("users", "readwrite");
let store = transaction.objectStore("users")
// 6、然后进行store.操作
// 增
store.add({"id": 1,"name": "YJL"})
store.add({"id": 2,"name": "XJ"})
store.add({"id": 33,"name": "111"})
// 删
store.delete(2)
// 改
store.put({"id": 3,"name": "111"})
// 查
let request2 = store.get(1)
request2.onsuccess = (e)=>{
console.log(request2.result)
}
};
// event.target.result 数据库(IDBDatabase)实例
// event.target 和 request 指向同一个对象
7、操作事件
增删改查操作会有各种事件
// 操作成功
request2.onsuccess = (event) => alert(event.target.result);
// 操作错误或取消
request2.onerror = (event) => { };
// 操作已全部完成
request2.oncomplete = (event) => { };
第三方库
第三方库,方便操作IndexedDB,可以通过SQL的方式增删改查,而且结果都是Promise的
Dexie.js
https://github.com/dfahlander/Dexie.js
https://dexie.org/