一般在项目中我们逗需要把信息存储在本地的情况,比如权限验证的token、用户信息、埋点计数、客户配置的皮肤信息或语言种类等,我们可以暂存一下避免浏览器不必要的请求和客户多余操作,较少请求从而提高性能以给客户使用带来方便。
那么浏览器存储有哪些方法呢,主要有cookie、localStorage、sessionStorage
cookie属于文档对象模型DOM树根节点document,而 sessionStorage 和 localStorage 属于浏览器对象模型BOM的对象window
其中 sessionStorage 和 localStorage 是 HTML5 Web Storage API 提供的
sessionStorage:为每一个给定的源(given origin)维持一个独立的存储区域,该存储区域在页面会话期间可用(即只要浏览器处于打开状态,包括页面重新加载和恢复)localStorage:同样的功能,但是在浏览器关闭,然后重新打开后数据仍然存在。
一、Cookie -会话跟踪技术
1、cookie的存取
document.cookie = "username=zhangsan";document.cookie = "username=zhangsan; expires=Thu, 18 Dec 12:00:00 GMT; path=/" console.log(document.cookie);
path
路径,值可以是一个目录,或者是一个路径。
如果/test/index.html 建立了一个cookie,那么在/test/目录里的所有页面,以及该目录下面任何子目录里的页面都可以访问这个cookie。因此在/test/test2/test3 里的任何页面都可以访问/test/index.html建立的cookie。若/test/ 若想访问/test/index.html设置的cookes,需要把cookies的path属性设置成“/”。
在指定路径的时候,凡是来自同一服务器,URL里有相同路径的所有WEB页面都可以共享cookies。
domain
主机名,是指同一个域下的不同主机,例如:和就是两个不同的主机名。默认情况下,一个主机中创建的cookie在另一个主机下是不能被访问的,但可以通过domain参数来实现对其的控制:document.cookie = “name=value;domain=.”
这样,所有*.的主机都可以访问该cookie。
expires
过期时间,当过了到期日期时,浏览器会自动删除该cookie,如果想删除一个cookie,只需要把它过期时间设置成过去的时间即可
比如希望设置过期时间一年:new Date().getTime() + 365 * 24 * 60 * 60 * 1000
如果不设置过期时间,则表示这个cookie生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie就消失了
2、特点
只能使用文本(如果浏览器可以随意在客户端机器上生成文件,比如身份牌是个定时炸弹,安全问题将会变得非常严重)单条存储有大小限制 4KB(文件若没有大小限制,比如身份牌的重量是140斤,挂脖子能不能累死?)数量限制一般浏览器,限制大概在50条左右,你家的门禁卡里能存的下一部蓝光高清么?读取有域名限制不可跨域读取,只能由来自 写入cookie的 同一域名 的网页可进行读取简单的讲就是,谁写的cookie,谁才有权利读取。(身份牌是我发你的,当然只有我能读取,你媳妇儿的手机自动连接了邻居老王家的wifi,你知道这意味着什么吗?)时效限制每个cookie都有时效,最短的有效期是,会话级别:就是当浏览器关闭,那么cookie立即销毁 。(安全学基本理论:密码锁每次打开都需要重新输入密码,输入一次密码,以后就不再验证,就没有安全可言 )
3.优缺点
1.IE6或更低版本最多20个cookie
2.IE7和之后的版本最后可以有50个cookie。
3.Firefox最多50个cookie
4.chrome和Safari没有做硬性限制
Opera 会清理近期最少使用的Firefox会随机清理 4096字节,为了兼容性,一般不能超过 IE 提供了一种存储可以持久化用户数据,叫做IE5.0就开始支持。每个数据最多128K,每个域名下最多1M。这个持久化数据放在缓存中,如果缓存没有清理,那么会一直存在。
优点:极高的扩展性和可用性
1.通过良好的编程,控制保存在cookie中的session对象的大小。
2.通过加密和安全传输技术(SSL),减少cookie被破解的可能性。
3.只在cookie中存放不敏感数据,即使被盗也不会有重大损失。
4.控制cookie的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的cookie。
缺点:
1.Cookie
数量和长度的限制。每个domain最多只能有20条cookie,每个cookie长度不能超过4KB,否则会被截掉。
2.安全性问题。如果cookie被人拦截了,那人就可以取得所有的session信息。即使加密也与事无补,因为拦截者并不需要知道cookie的意义,他只要原样转发cookie就可以达到目的了。
3.有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
4、插件
Vue
名称:js-Cookie
npm:npm install js-cookie --save
地址:/package/js-cookie
React
名称:react-cookies
npm:npm install react-cookies --save
地址: /package/react-cookies
二、localStorage
以键值对(Key-Value)的方式存储,永久存储,永不失效,除非手动删除。IE8+支持,每个域名限制5M。
1、存取
window.localStorage.username = 'hehe' // 设置window.localStorage.setItem('username', 'hehe') // 设置window.localStorage.getItem('username') // 读取window.localStorage.removeItem('username') // 删除window.localStorage.key(1) // 读取索引为1的值window.localStorage.clear() // 清除所有
2、localStorage 的优势
1、localStorage 拓展了 cookie 的 4K 限制。
2、localStorage 会可以将第一次请求的数据直接存储到本地,这个相当于一个 5M 大小的针对于前端页面的数据库,相比于 cookie 可以节约带宽,但是这个却是只有在高版本的浏览器中才支持的。
3、localStorage 的局限
1、浏览器的大小不统一,并且在 IE8 以上的 IE 版本才支持 localStorage 这个属性。
2、目前所有的浏览器中都会把localStorage的值类型限定为string类型,这个在对我们日常比较常见的JSON对象类型需要一些转换。
3、localStorage在浏览器的隐私模式下面是不可读取的。
4、localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡。
5、localStorage不能被爬虫抓取到。
localStorage 与 sessionStorage 的唯一一点区别就是 localStorage 属于永久性存储,而 sessionStorage 属于当会话结束的时候,sessionStorage 中的键值对会被清空。
4、localStorage 使用前
首先在使用 localStorage 的时候,我们需要判断浏览器是否支持 localStorage 这个属性:
if(! window.localStorage){alert("浏览器不支持localstorage");return false;}else{//主逻辑业务}
写法
if(!window.localStorage){alert("浏览器不支持localstorage");return false;}else{var storage=window.localStorage;//写入a字段storage["a"]=1;//写入b字段storage.b=1;//写入c字段storage.setItem("c",3);console.log(typeof storage["a"]);console.log(typeof storage["b"]);console.log(typeof storage["c"]);}
5、localStorage 键的获取
使用 key() 方法,向其中出入索引即可获取对应的键。
var storage = window.localStorage;storage.a = 1;storage.setItem("c",3);for (var i=0;i<storage.length;i++) {var key = storage.key(i);console.log(key);}
6、localStorage 其他注意事项
一般我们会将 JSON 存入 localStorage 中,但是在localStorage 会自动将 localStorage 转换成为字符串形式。
这个时候我们可以使用JSON.stringify()
这个方法,来将 JSON 转换成为 JSON 字符串。
if (!window.localStorage) {alert("浏览器不支持localstorage");} else {var storage = window.localStorage;var data = {name:'xiecanyong',sex:'man',hobby:'program'};var d = JSON.stringify(data);storage.setItem("data",d);console.log(storage.data);}
读取之后要将 JSON 字符串转换成为 JSON 对象,使用JSON.parse()
方法:
var storage = window.localStorage;var data = {name:'xiecanyong',sex:'man',hobby:'program'};var d = JSON.stringify(data);storage.setItem("data",d);//将JSON字符串转换成为JSON对象输出var json = storage.getItem("data");var jsonObj = JSON.parse(json);// 打印出来是 Object 对象console.log(typeof jsonObj);
三、sessionStorage
sessionStorage操作的方法与localStroage是一样的,区别在于 sessionStorage 在关闭页面后即被清空,而 localStorage 则会一直保存。很多时候数据只需要在用户浏览一组页面期间使用,关闭窗口后数据就可以丢弃了,这种情况使用sessionStorage就比较方便。
注意,刷新页面sessionStorage不会清除,但是打开同域新页面访问不到。
存取
window.sessionStorage.username = 'hehe' // 设置window.sessionStorage.setItem('username', 'hehe') // 设置window.sessionStorage.getItem('username') // 读取window.sessionStorage.removeItem('username') // 删除window.sessionStorage.key(1) // 读取索引为1的值window.sessionStorage.clear() // 清除所有
四、web storage和cookie的区别
cookie相似,区别是它是为了更大容量存储设计的。Cookie都会被发送过去,这样无形中浪费了带宽,另外 除此之外,setItem
,getItem
,removeItem
,clear
等方法,不像setCookie
,getCookie
。
但是Cookie的作用是与服务器进行交互,作为Web Storage仅仅是为了在本地“存储”数据而生
浏览器的支持除了UserData其实就是web storage。
sessionStorage都具有相同的操作方法,例如removeItem
等。
五、IndexedDB
IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
1、特点
IndexedDB 具有以下特点。
键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以"键值对"的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
同源限制。 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
储存空间大。 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
2、概念
IndexedDB 是一个比较复杂的 API,涉及不少概念。它把不同的实体,抽象成一个个对象接口。学习这个 API,就是学习它的各种对象接口。
数据库:IDBDatabase 对象对象仓库:IDBObjectStore 对象索引: IDBIndex 对象事务: IDBTransaction 对象操作请求:IDBRequest 对象指针: IDBCursor 对象主键集合:IDBKeyRange 对象
下面是一些主要的概念。
(1)数据库
数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。
IndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。
(2)对象仓库
每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格。
(3)数据记录
对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分。主键用来建立默认的索引,必须是不同的,否则会报错。主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数编号。
{id: 1, text: 'foo' }
上面的对象中,id属性可以当作主键。
数据体可以是任意数据类型,不限于对象。
(4)索引
为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。
(5)事务
数据记录的读写和删改,都要通过事务完成。事务对象提供error、abort和complete三个事件,用来监听操作结果。
3、操作流程
IndexedDB 数据库的各种操作,一般是按照下面的流程进行的。这个部分只给出简单的代码示例,用于快速上手,详细的各个对象的 API 请看这里。
3.1 打开数据库
使用 IndexedDB 的第一步是打开数据库,使用indexedDB.open()
方法。
// indexedDB.open('name', 'version') // 返回一个 IDBRequest 对象var request = window.indexedDB.open(databaseName, version);
这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1。
indexedDB.open()
方法返回一个 IDBRequest 对象。这个对象通过三种事件error
、success
、upgradeneeded
,处理打开数据库的操作结果。
(1)error 事件
error事件表示打开数据库失败。
request.onerror = function (event) {console.log('数据库打开报错');};
(2)success 事件
success事件表示成功打开数据库。
var db;request.onsuccess = function (event) {db = request.result;console.log('数据库打开成功');};
这时,通过request对象的result属性拿到数据库对象。
(3)upgradeneeded 事件
如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded。
var db;request.onupgradeneeded = function (event) {db = event.target.result;}
这时通过事件对象的target.result
属性,拿到数据库实例。
3.2 新建数据库
新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded
事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。
通常,新建数据库以后,第一件事是新建对象仓库(即新建表)。
request.onupgradeneeded = function(event) {db = event.target.result;var objectStore = db.createObjectStore('person', {keyPath: 'id' });}
上面代码中,数据库新建成功以后,新增一张叫做person的表格,主键是id。
更好的写法是先判断一下,这张表格是否存在,如果不存在再新建。
request.onupgradeneeded = function (event) {db = event.target.result;var objectStore;if (!db.objectStoreNames.contains('person')) {objectStore = db.createObjectStore('person', {keyPath: 'id' });}}
主键(key)是默认建立索引的属性。比如,数据记录是 { id: 1, name: ‘张三’ },那么 id 属性可以作为主键。主键也可以指定为下一层对象的属性,比如 { foo: { bar: ‘baz’ } } 的 foo.bar 也可以指定为主键。
如果数据记录里面没有合适作为主键的属性,那么可以让 IndexedDB 自动生成主键。
var objectStore = db.createObjectStore('person',{autoIncrement: true });
上面代码中,指定主键为一个递增的整数。
新建对象仓库以后,下一步可以新建索引。
request.onupgradeneeded = function(event) {db = event.target.result;var objectStore = db.createObjectStore('person', {keyPath: 'id' });objectStore.createIndex('name', 'name', {unique: false });objectStore.createIndex('email', 'email', {unique: true });}
上面代码中,IDBObject.createIndex()
的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)。
3.3 新增数据
新增数据指的是向对象仓库写入数据记录。这需要通过事务完成。
function add() {var request = db.transaction(['person'], 'readwrite').objectStore('person').add({id: 1, name: '张三', age: 24, email: 'zhangsan@' });request.onsuccess = function (event) {console.log('数据写入成功');};request.onerror = function (event) {console.log('数据写入失败');}}add();
上面代码中,写入数据需要新建一个事务。新建时必须指定表格名称和操作模式(“只读"或"读写”)。新建事务以后,通过IDBTransaction.objectStore(name)
方法,拿到 IDBObjectStore 对象,再通过表格对象的add()方法,向表格写入一条记录。
写入操作是一个异步操作,通过监听连接对象的success
事件和error
事件,了解是否写入成功。
3.4 读取数据
读取数据也是通过事务完成。
function read() {var transaction = db.transaction(['person']);var objectStore = transaction.objectStore('person');var request = objectStore.get(1);request.onerror = function(event) {console.log('事务失败');};request.onsuccess = function( event) {if (request.result) {console.log('Name: ' + request.result.name);console.log('Age: ' + request.result.age);console.log('Email: ' + request.result.email);} else {console.log('未获得数据记录');}};}read();
上面代码中,objectStore.get()
方法用于读取数据,参数是主键的值。
3.5 遍历数据
遍历数据表格的所有记录,要使用指针对象 IDBCursor。
function readAll() {var objectStore = db.transaction('person').objectStore('person');objectStore.openCursor().onsuccess = function (event) {var cursor = event.target.result;if (cursor) {console.log('Id: ' + cursor.key);console.log('Name: ' + cursor.value.name);console.log('Age: ' + cursor.value.age);console.log('Email: ' + cursor.value.email);cursor.continue();} else {console.log('没有更多数据了!');}};}readAll();
上面代码中,新建指针对象的openCursor()方法是一个异步操作,所以要监听success
事件。
3.6 更新数据
更新数据要使用IDBObject.put()
方法。
function update() {var request = db.transaction(['person'], 'readwrite').objectStore('person').put({id: 1, name: '李四', age: 35, email: 'lisi@' });request.onsuccess = function (event) {console.log('数据更新成功');};request.onerror = function (event) {console.log('数据更新失败');}}update();
上面代码中,put()方法自动更新了主键为1的记录。
3.7 删除数据
IDBObjectStore.delete()
方法用于删除记录。
function remove() {var request = db.transaction(['person'], 'readwrite').objectStore('person').delete(1);request.onsuccess = function (event) {console.log('数据删除成功');};}remove();
3.8 使用索引
索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。
假定新建表格的时候,对name字段建立了索引。
objectStore.createIndex('name', 'name', { unique: false })
;
现在,就可以从name找到对应的数据记录了。
var transaction = db.transaction(['person'], 'readonly');var store = transaction.objectStore('person');var index = store.index('name');var request = index.get('李四');request.onsuccess = function (e) {var result = e.target.result;if (result) {// ...} else {// ...}}