介绍
浏览器现在提供很多存储方法,比如cookie
,localStorage
,sessionStorage
, indexedDB
等。但是他们有什么区别呢?今天我们一起复习一下。
HTTP Cookies
首先cookie是服务器向浏览器设置的一些标识记录,通常这些数据并不大,比如用户令牌。那么我们如何在服务端向客户设置一个cookie呢?我们需要使用到Set-Cookie
的选项
Set-Cookie
响应头中如果携带Set-Cookie
那么表示对客户端的cookie
进行设置。我们来看一下设置的语法
Set-Cookie: <cookie-name>=<cookie-value>
Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>
Set-Cookie: <cookie-name>=<cookie-value>; Max-Age=<non-zero-digit>
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>
Set-Cookie: <cookie-name>=<cookie-value>; Path=<path-value>
Set-Cookie: <cookie-name>=<cookie-value>; Secure
Set-Cookie: <cookie-name>=<cookie-value>; HttpOnly
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Strict
Set-Cookie: <cookie-name>=<cookie-value>; SameSite=Lax
// 多指令案例如下:
Set-Cookie: <cookie-name>=<cookie-value>; Domain=<domain-value>; Secure; HttpOnly
复制代码
实际案例(来自百度):
OK 首先我们可以看到 基本上是以健值对的方式传递到,数据后可以以;
进行分割对相应的数据进行相关的配置,配置项包含下列(来自MDN):
- Expires=<date>
cookie 的最长有效时间,形式为符合 HTTP-date 规范的时间戳。参考 Date
可以获取详细信息。如果没有设置这个属性,那么表示这是一个会话期 cookie 。一个会话结束于客户端被关闭时,这意味着会话期 cookie 在彼时会被移除。然而,很多Web浏览器支持会话恢复功能,这个功能可以使浏览器保留所有的tab标签,然后在重新打开浏览器的时候将其还原。与此同时,cookie 也会恢复,就跟从来没有关闭浏览器一样。
- Max-Age=<non-zero-digit>
在 cookie 失效之前需要经过的秒数。秒数为 0 或 -1 将会使 cookie 直接过期。一些老的浏览器(ie6、ie7 和 ie8)不支持这个属性。对于其他浏览器来说,假如二者 (指 Expires
和Max-Age
) 均存在,那么 Max-Age 优先级更高。
- Domain=<domain-value>
指定 cookie 可以送达的主机名。假如没有指定,那么默认值为当前文档访问地址中的主机部分(但是不包含子域名)。与之前的规范不同的是,域名之前的点号会被忽略。假如指定了域名,那么相当于各个子域名也包含在内了。
- Path=<path-value>
指定一个 URL 路径,这个路径必须出现在要请求的资源的路径中才可以发送 Cookie 首部。字符 %x2F ("/") 可以解释为文件目录分隔符,此目录的下级目录也满足匹配的条件(例如,如果 path=/docs,那么 "/docs", "/docs/Web/" 或者 "/docs/Web/HTTP" 都满足匹配的条件)。
- Secure
一个带有安全属性的 cookie 只有在请求使用SSL和HTTPS协议的时候才会被发送到服务器。然而,保密或敏感信息永远不要在 HTTP cookie 中存储或传输,因为整个机制从本质上来说都是不安全的,比如前述协议并不意味着所有的信息都是经过加密的。
注意: 非安全站点(http:)已经不能再在 cookie 中设置 secure 指令了(在Chrome 52+ and Firefox 52+ 中新引入的限制)。
- HttpOnly
设置了 HttpOnly 属性的 cookie 不能使用 JavaScript 经由 Document.cookie
属性、XMLHttpRequest
和 Request
APIs 进行访问,以防范跨站脚本攻击(XSS (en-US))。
- SameSite=Strict; SameSite=Lax; SameSite=None;
允许服务器设定一则 cookie 不随着跨域请求一起发送,这样可以在一定程度上防范跨站请求伪造攻击(CSRF)。
同时Cookie的cookie-name
针对前缀还有一些特殊的限制,如下:
-
__Secure-
前缀:以 __Secure- 为前缀的 cookie(其中连接符是前缀的一部分),必须与 secure 属性一同设置,同时必须应用于安全页面(即使用 HTTPS 访问的页面)。 -
__Host-
前缀: 以 __Host- 为前缀的 cookie,必须与 secure 属性一同设置,必须应用于安全页面(即使用 HTTPS 访问的页面),必须不能设置 domain 属性 (也就不会发送给子域),同时 path 属性的值必须为“/”。
存储空间
通常来说,相同域下的cookie总大小不会大于4kb,并且不同的浏览器保有的cookie个数是不一样的。保险来说尽可能的少去使用cookie总是没错的。
操作Cookie
那么我们是否可以在JS中操作cookie呢?
答案: 可以,但是需要区分不同的情况,大部分情况下不要这么去做。我们可以通过docoment.cookie
访问到我们当前域下所记录的cookie。但是这些cookie信息是不包含配置项。我们直接打印docoment.cookie
,通常不太方便于我们的阅读,我们可以通过类似的方式,让我们更加直观的查看每条cookie数据document.cookie.split(';').map(d => d.trim());
。
我们可以通过如下语句对cookie进行设置:
document.cookie = `myCookie=1`
document.cookie = `uCookie=2`
// ******; myCookie=1; uCookie=2
console.log(document.cookie)
复制代码
当然也可以携带以上的相关配置项:
document.cookie = `myCookie=1; max-age=10000;`
复制代码
然后我们打开Chrome DevTools
的 应用(Aplication) 查看存活时间是否生效
我们可以看到已经成功的设置了对应的配置项
总结
那么到这里我们基本就完成了对于cookie的学习,其实对于Web前端开发而言,我们对于cookie并没有很多的操作空间,更多的是防范因为使用cookie而导致的安全漏洞,比如XSS
或者CSRF
等,后续我会针对前端安全做一些文章。到时候再来分享
Web Storage API
包括我们熟知的sessionStorage
和localStorage
都属于Web Storage API
。
他们都是属于浏览器定义的Storage
对象,区别在于sessionStorage
是会话级别的存储。而localStorage
属于持久化存储(不主动清除便永久存在)。
操作Storage
Storage
是针对同源环境下浏览器存储,那么它存在以下的方法(来自MDN)
-
该方法接受一个数值 n 作为参数,并返回存储中的第 n 个键名。
-
该方法接受一个键名作为参数,返回键名对应的值。
-
该方法接受一个键名和值作为参数,将会把键值对添加到存储中,如果键名存在,则更新其对应的值。
-
该方法接受一个键名作为参数,并把该键名从存储中删除。
-
调用该方法会清空存储中的所有键名。
存储空间
通常来说每个源下的Storage
有5M的存储空间,当然基于浏览器不同,存储空间也可能有所差异,通常我们可以通过存储时抛出异常中是否包含'exceeded'
进行判断。如果我们需要对大数据,文件进行存储
事件
注意: 使用web Storage api
可以触发StorageEvent
的。但是这里有一个注意点,就是如果你是顶级窗口 window.top === window
是无法触发事件的,需要同源窗口触发Storage
才能触发事件,比如说:
- 当前窗口下的iframe加载同源地址中使用的
Web Storage Api
- 当前浏览器的其他同源地址标签中使用
Web Storage Api
以上场景都会触发StorageEvent
事件
IndexedDB
用于大数据存储的浏览器存储,是一个事务型数据库系统。它与Web Storage API
不同的是,它是一个基于异步的存储API。我们可以发现基本所有的操作都会返回IDBRequest
类型对象。当然我相信肯定有一些完善的第三方库已经将基础的Event
包装成Promise
。不过我们还是一起来学习一下如何使用IndexedDB
操作
创建数据库连接
IndexedDB
是没有办法类似Web Storage API
一样直接进行操作的,他需要你主动进行open
才可以对数据库进行操作。open
方法接受两个参数,第一个参数为对应的数据库名称,如果不存在的话会为其创建一个新的数据库,版本号为1
。第二个参数为版本号,版本号需要为一个unsigned long long
的数字,类型定义点击这里查看。不传即为打开已有版本的数据库。
// 打开名为"myDateBase"的数据库,版本号为1
const IDBOpenDBRequest = window.indexedDB.open('myDateBase', 1)
复制代码
那么我们看到我们调用open
得到IDBOpenDBRequest
,IDBOpenDBRequest
继承于IDBRequest 让我们分别来了解一下IDBRequest
与 IDBOpenDBRequest
。
IDBRequest
首先基于IndexedDB
的所有异步操作都会得到一个IDBRequest
, IDBRequest
继承于EventTarget
,本身属于一个事件目标对象。除去事件相关的方法外。它本身具有以下属性/事件
属性
- result
请求的结果集, 如果对应结果不可用的话会抛出InvalidStateError
的异常
- error
对应异步请求过程中发生的错误,不同错误抛出对应的原因可以点击这里进行查看
- source
请求的来源通常是Index
或者ObjectStore
,如果为null
那么通常是使用的IDBFactory.open
- transaction
请求的事务。对于某些请求,此属性可以为 null,例如从 IDBFactory.open
返回的请求
- readyState
请求状态只有pending
跟done
(注意:即使发生错误也为done
)
事件
- error
错误,打开失败,可以通过error
字段了解错误信息
- success
请求成功,可以通过event.target.result
得到对应的请求数据
了解完IDBRequest
我们看看IDBOpenDBRequest
有定义了什么内容
IDBOpenDBRequest
它在IDBRequest
的基础上又增加两个事件分别是:
- blocked
当upgradeneeded
事件应该执行是,如果数据库还是保持使用状态会触发blocked
事件
- upgradeneeded
当open
的数据库版本,比当前存在的数据版本高或者首次创建时会触发当前事件
这里我们完成对于IDBOpenDBRequest
的学习,接着我们学习如何创建数据对象
数据库实例对象
那么我们在打开数据库后通过,先获取数据库实例对象
let db = null
const IDBOpenDBRequest = window.indexedDB.open('myDateBase', 1)
IDBOpenDBRequest.onerror = function (event) {
throw IDBOpenDBRequest.error
}
IDBOpenDBRequest.onsuccess = function (event) {
db = event.target.result
// IDBDatabase实例对象
console.log(db)
}
复制代码
IDBDatabase
是IDB的中数据库的实例对象,我们来一起学习一下他的属性/方法/事件
IDBDatabase
属性
IDBDatabase具有三个属性分别如下
- name
当前数据库的名字
- version
当前连接的数据库的版本
- objectStoreNames
当前所拥有的objectStore
的名称的类数组对象
方法
在一个单独的线程中关闭数据库连接并立即返回。
创建并返回一个新的IDBObjectStore
根据给定的名字,删除在当前连接的数据库中的IDBObjectStore
和相关的索引。
立即返回一个包含IDBTransaction.objectStore 方法的 transaction 对象。你可以用这个对象来操作object store。这个操作是在一个单独的线程中执行的。
事件
- abort
数据库中断事件
- error
反问数据库失败事件
- versionchange
当数据库结构发生变化时触发(可能在upgradeneeded
或者在请求deleteDatabase
时触发)
了解完IDBDatabase
,由于我们在IDB中的数据是通过IDBObjectStore
去进行存储的,所以我们接下来看看如何创建IDBObjectStore
创建IDBObjectStore
通常我们会在upgradeneeded
事件下去创建我们的对象仓储,当然大家也可以结合自己的业务需求动态的对数据仓储进行动态创建,我们来看完整代码。
let db = null
const IDBOpenDBRequest = window.indexedDB.open('myDateBase', 1)
IDBOpenDBRequest.onerror = function (event) {
throw IDBOpenDBRequest.error
}
IDBOpenDBRequest.onsuccess = function (event) {
db = event.target.result
setData()
}
IDBOpenDBRequest.onupgradeneeded = function (event) {
event.target.result.createObjectStore('myStore', {
keyPath: 'id',
autoIncrement: true
})
}
function setData () {
const ts = db.transaction('myStore', 'readwrite')
const myStore = ts.objectStore('myStore')
myStore.add({ anyValue: 1 })
ts.oncomplete = function () {
console.log('finish transaction')
}
}
复制代码
运行以上代码后,我们就可以在我们的Chrome DevTools
查看IndexedDB
的使用情况
会得到类似的数据结构,在以上代码中,出现了两个新的数据对象分别是IDBObjectStore
和IDBTransaction
IDBObjectStore
可以理解为Mysql
数据库中的表的概念,可以对其中的数据进行增删查改等操作,同时可以类似Mysql
中表的列行为,指定数据对应的Index
,并且通过Index
对数据进行查找,具体的方法/属性如下
属性
- indexNames
索引列表,返回索引的Name,这样可以通过方法,index(name)得到对应的IDBIndex对象,然后通过IDBIndex对象去去对指定索引的数据进行查询。
- keyPath
指定的主索引路径,如果指定的当前路径,那么存储数据大多为对象
- name
对象仓储名称
- transation
当前数据对象所属的事务名称
- autoIncrement
表中的主索引是否自增
方法
IDBRequest add (in any value, in optional any key) raises (DOMException);
添加数据方法,第一个参数指定添加的数据。注意如果创建数据仓储时有指定keyPath
并且autoIncrement
不设定或者设定为false
时,value中需要包含此属性。第二个参数为指定的key
,如果在创建数据仓储不指定keyPath
时则需要传递次参数。
IDBRequest clear () raises (DOMException);
清楚当前数据仓储中的所有数据
IDBRequest count (in optional any key) raises``(DOMException);
相当于group by id
然后 count(id)
得出的数据。
IDBIndex createIndex (in DOMString name, in ``DOMString`` keyPath, in optional boolean unique) raises (DOMException);
针对数据仓储创建对应的索引
IDBRequest delete (in any key) raises
(DOMException);
删除对应索引的数据
void deleteIndex (in any ``DOMString`` indexName) raises (DOMException);
删除对应的索引
IDBRequest get (in any key) raises (DOMException);
获取主索引相当于key
的数据
IDBIndex index (in ``DOMString`` name) raises (DOMException);
获取对应的IDBIndex
对象。
IDBRequest openCursor (in optional IDBKeyRange range, in optional unsigned short direction) raises(DOMException);
打开游标,传入IDBKeyRange
对象,返回对应的数据游标。direction
指定游标的方向。
IDBRequest put (in any value, in optional any key) raises (DOMException);
修改对应索引的数据。
IDBTransaction
可以从IDBDatabase
创建一个对应的事务
,事务的概念大家可以自行学习,我们可以在创建的事务的时候可以指定对应的对象仓储以及模式。模式中包含:readonly
,readwrite
,versionchange
readonly
仅可读取数据
readwrite
对数据可读可写
versionchange
允许做任何事务
除此外对象中还有对应的属性/方法/事件
方法
放弃本次连接的transaction的所有修改,如果当前的transaction处于回滚完毕或完成状态,则会抛出一个错误事件。
返回表示作为此事务作用域一部分的object store的 IDBObjectStore
对象。
属性
当前事务所属的数据库连接。
错误原因
用于隔离事务作用域内的object store中数据访问的模式。下方的常量章节给出了所有可用的值。默认值是 readonly
。
返回所有的objectStore
的name
。
事件
事务中断事件
完成事务
错误事务
到这里我们基本上完成,对于IndexedDB
的基础操作。
OK,那么我们这里就完成对于浏览器的存储的基础学习。 同时到这里我们关于JS的复习也到一段落,我们接下来要开始对HTML
的学习
下一章:面试学习-HTML