浏览器提供的几种存储
随着移动网络的发展与演化,我们手机上现在除了有原生APP,还能跑WebApp,它即开即用,用完即走。一个优秀的WebApp甚至可以拥有和原生App媲美的功能和体验。WebApp优异的性能表现,有一部分原因要归功于浏览器存储技术的提升。Cookie存储数据的功能已经很难满足开发所需,逐渐被WebStorage和IndexedDB取代。
Cookie
什么是Cookie
Cookie是指某些网站为了辨别用户身份而存储在用户本地终端上的数据(通常经过加密)。Cookie是由服务器生成,客户端进行维护和存储。通过Cookie,可以让服务器知道请求是来源自哪个客户端并对客户端状态进行维护。比如登录后的刷新和跳转,请求头会携带登陆时候Response Header中的set-cookie属性,Web服务器在接收请求的时候就能读出Cookie中的值,根据Cookie中的值就可以判断和恢复一些用户的信息状态。
通过上面的描述我们可以知道,Cookie的本职工作并非是本地存储,而是状态维持。因为HTTP协议是无状态的,不会保存请求和响应之间的通信状态。这就意味着,服务器在这种无状态交互下,不会知道用户上一次做了什么,也就阻碍了交互式Web应用程序的实现。Cookie就是为了绕开HTTP的无状态性而诞生的一种解决方案,服务器可以通过设置和读取Cookie中包含的用户信息来维持用户与服务器会话中的状态。可以把Cookie理解为一个存储在浏览器中的小体积的文本文件,在请求的时候将携带着用户信息的Cookie附着在HTTP请求上,服务器就可以获取到客户端的状态。
Cookie的典型应用场景有自动登录(记住密码)、购物车功能、记录用户行为(做商品/广告推荐)等。
Cookie的原理与生成方式
举个简单的例子,在典型的网上购物场景中,用户浏览了几个页面,买了一盒饼干和两瓶饮料,最后结账的时候,由于HTTP的无状态性,服务器根本不能知道用户到底买了什么,或者说用户和用户购买的商品无法对应上。而有了Cookie之后,当用户成功登录之后,服务器在返回成功登录响应的时候就会给客户端返回一个携带用户信息的Cookie,客户端会保存这个Cookie。当用户选购了商品并发送请求,请求中就会带上这个Cookie,然后服务器就可以检查Cookie并获取Cookie中的用户信息,让用户和购买的商品能够对应得上。
第一次访问网站的时候,浏览器发出请求,服务器响应请求后,会在响应头里面添加一个set-cookie选项,将Cookie放入到响应请求中,在浏览器第二次发请求的时候,会通过Cookie请求头部将Cookie信息发送给服务器,服务端会辨别用户身份,另外,Cookie的过期时间、域、路径、有效期、适用站点都可以根据需要来指定。
Cookie的生成方式主要有以下两种:
1.http response header中的set-cookie。
2.js中通过document.cookie读写cookie,可以将cookie解析为键值对的形式展示。
Cookie的缺陷
1.Cookie不够大。Cookie的大小限制在4KB左右,对于复杂的存储需求来说是不够用的。当Cookie超过4KB的时候,它将会面临被裁切的命运。这样看来,Cookie只能用来存取少量的信息,甚至有些浏览器对一个站点的Cookie个数都有做限制。这里要注意的是,这里的4KB容量限制是对Cookie中name=value的value来说的,并不是一个域名下所有的Cookie共享的。
2.过多的Cookie会带来巨大的性能/资源浪费。Cookie是紧跟域名的,同一个域名下的所有请求都会携带Cookie。很多场景其实并不需要身份验证,比如请求一张图片或者CSS文件,这种时候携带Cookie也就造成了性能/资源的浪费。有个解决方案就是使用CDN作为静态资源文件的请求服务方式,因为CDN的域名与主站的域名是分开的,也就不会在请求中附带Cookie了。
Cookie的安全性
虽然Cookie中有些属性可以适当维持安全性,但是其不安全性几乎仍然是固有的。
为了弥补Cookie的局限性,WebStorage就产生了。WebStorage是HTML5中新增的本地存储解决方案,包含了SessionStorage和LocalStorage两类。有了WebStorage之后,Cookie就能够安心只作为客户端与服务器交互的通道(保持客户端状态)了,不用再操心存储的事情。
LocalStorage
LocalStorage是WebStorage之一。
LocalStorage的特点
1.保存的数据长期存在,下一次访问该网站的时候,网页可以直接读取之前保存的数据。
2.存储限制大小为5MB左右。
3.仅在客户端使用,不和服务端进行通信。
4.接口封装得较好。
基于上面的特点,LocalStorage可以作为浏览器的本地缓存方案,用来提升网页首屏渲染速度(根据第一请求返回时,将一些不变的信息直接存储在本地)。
LocalStorage存入/读取数据
LocalStorage保存的数据是以键值对的形式存在的,也就是说,每一项数据都有一个键名和对应的值,且所有的数据都是以文本格式保存的。
存入数据使用setItem()方法。这个方法接受两个参数,第一个是键名,第二个是保存的数据。
localStorage.setItem(key, value);
读取数据使用getItem()方法。这个方法接受一个参数,就是键名。
localStorage.getItem(key);
来看一个完整的例子:
function testLocalStorage() { if (window.localStorage) { localStorage.setItem('name', 'yanggb'); console.log(localStorage.getItem('name')); } }
LocalStorage的使用场景
LocalStorage在存储方面几乎没有什么特别的限制,在理论上,一些Cookie无法胜任的、存储简单键值对的存储任务都可以交给LocalStorage来做。
考虑到LocalStorage的特点之一是持久,因此有时候我们更倾向于用它来存储一些内容稳定的资源。比如图片内容丰富的电商网站可以使用LocalStorage来存储Base64格式的图片字符串。
SessionStorage
SessionStorage保存的数据用于浏览器的一次会话,当会话结束(通常是该窗口关闭),数据就会被清空。SessionStorage特别的一点在于,即便是相同域名下的两个页面,只要它们不在同一个浏览器窗口中打开,那么它们的SessionStorage内容便无法共享,而LocalStorage在所有同源窗口中共享的,Cookie也是在所有同源窗口中共享的。除了保存期限的长短不同,SessionStorage的属性和方法与LocalStorage完全一样。
SessionStorage的特点
1.会话级别的浏览器存储。
2.存储大小限制为5MB左右。
3.仅在客户端使用,不与服务端进行通信。
4.接口封装得较好。
基于上面的特点,SessionStorage可以有效地对表单信息进行缓存,比如刷新时表单的信息不会丢失。
SessionStorage的使用场景
SessionStorage更适合用来存储生命周期和它同步的会话级别的信息,这些信息只适用于当前的会话。当你开启新的会话时,它也需要相应的更新或释放。比如微博就使用SessionStorage存储单次会话的浏览足迹,它会存一个lasturl键去对应你上一次访问的url地址。当你切换url的时候,这个键值就会更新;当你关闭页面的时候,这个键就会被释放。这样的数据用SessionStorage来处理再合适不过了。
SessionStorage、LocalStorage和Cookie之间的区别
SessionStorage、LocalStorage和Cookie都是存储在浏览器端,都遵循浏览器的同源策略,不同的地方在于它们的生命周期和作用域不同。
作用域的不同
LocalStorage只要在相同的协议、相同的主机名、相同的端口下,就能读取/修改到同一份LocalStorage数据。而SessionStorage则比LocalStorage要更严苛一点,除了协议、主机名、端口外,还要求在同一窗口(也就是浏览器的标签页)下。
生命周期的不同
LocalStorage是持久化的本地存储,存储在其中的数据是永远不会过期的,使其消失的唯一办法是手动删除;而SessionStorage是临时性的本地存储,是会话级别的存储,当会话结束(页面被关闭)时,存储内容也会随之被释放。
Web Storage是一个从定义到使用都非常简单的东西。它使用键值对的形式进行存储,这种模式有点类似于对象,可是键值却只能存储字符串。如果想要得到对象,还需要先对字符串进行一轮解析。因此Web Storage可以看作是对Cookie的拓展,只能用于存储少量的简单数据。当遇到大规模的、结构复杂的数据时,就需要用到IndexedDB。
IndexedDB
IndexedDB是一种低级的API,使用索引来实现对其间存储数据的高性能搜索,用于客户端需要存储大量结构化数据(包括文件和blobs)的场景。
IndexedDB可以看作是一个运行在浏览器上的非关系型数据库。既然是数据库了,那就不是5M、10M这样小打小闹的级别了,理论上来说,IndexedDB是没有存储上限的,一般来说不会小于250M。另外,它不仅可以存储字符串,还可以存储二进制数据。
IndexedDB的特点
1.键值对存储。IndexedDB内部采用对象仓库(Object Store)存放数据,所有类型的数据都可以直接存入,包括JavaScript对象。在对象仓库中,数据以键值对的形式保存,每一个数据记录都有对应的主键,且主键是唯一的,不可重复,否则会抛出错误。
2.异步。IndexedDB在操作时不会锁死浏览器(异步线程,不会挂起其他线程),用户依然可以可以进行其他操作。这与LocalStorage形成对比,后者的操作是同步的(会挂起其他线程)。异步设计是为了防止读写大量数据的时候拖慢网页的表现性能。
3.支持事务。IndexedDB是支持事务(Transaction)的,也同样支持事务的ACID特性。这意味着在一系列的操作步骤中,只要有一步失败,整个事务就会取消,数据库将回滚到事务发生之前的状态,不存在只改写一部分数据的情况,保证数据的完整性和一致性。
4.同源限制。IndexedDB受浏览器同源策略的限制,每一个数据库都严格对应创建它的域名。网页只能访问自身域名下面的数据库,而不能访问跨域的数据库。
5.存储空间大。IndexedDB的存储空间比LocalStorage大得多,一般来说不小于250MB,甚至没有上限。
6.支持二进制存储。IndexedDB不仅可以存储字符串,还可以存储二进制数据(ArrayBuffer对象和Blob对象)。
IndexedDB的创建、关闭和删除操作
在IndexedDB的大部分操作并不是我们常用的调用方法(返回结果的模式),而是请求-响应的模式。
1.建立打开IndexedDB。
window.indexedDB.open("testdb");
上面这条语句并不会返回一个DB对象的句柄,我们得到的是一个IDBOpenDBRequest对象,在这个对象中的result属性中会包含我们想要的DB对象。
除了result之外,IDBOpenDBRequest接口还定义了几个重要的属性。
属性 | 值 |
onerror | 请求失败的回调函数句柄 |
onsuccess | 请求成功的回调函数句柄 |
onupgradeneeded | 请求数据库版本变化句柄 |
来看一个完整的例子:
// 全局myDB对象 var myDB = { name: 'testdb', db: null } // 定义创建打开indexedDB的函数 function openDB (dbName) { var request = window.indexedDB.open(dbName); // 创建打开失败事件 request.onerror = function(e) { console.log('open indexdb error'); } // 创建打开成功事件 request.onsuccess = function(e) { myDB.db = e.target.result; console.log(myDB.db); } } // 调用创建打开函数 openDB(myDB.name);
2.关闭IndexedDB。
// 定义关闭IndexedDB函数 function closeDB() { myDB.db.close(); }
这里要注意调用close()方法的是IndexedDB对象,且关闭并不会删除浏览器中的IndexedDB数据库。
3.删除IndexedDB。
// 定义删除IndexedDB的函数 function deleteDB() { window.indexedDB.deleteDatabase(myDB.name); }
要特别提及的是,IndexedDB的命名是严格区分大小写的,无论是创建还是删除都要注意这一点。
关于IndexedDB的使用,内容比较多,另外开篇去深入了解。
Cookie、WebStorage和IndexedDB之间的区别
从上表中可以看出,Cookie已经不建议用于存储用途。如果没有大量数据存储需求的话,可以使用LocalStorage和SessionStorage。对于不怎么改变的数据尽量使用LocalStorage做存储,否则可以用SessionStorage做存储。
浏览器存储的总结
因为浏览器存储和缓存技术的出现和发展,前端应用带来了无限的可能。近年来,基于存储、缓存技术的第三方库层出不穷,此外还衍生出了PWA这样优秀的Web应用模型。
1.Cookie的本质工作并不是本地存储,而是状态维持。
2.WebStorage是HTML5专门为浏览器存储而提供的数据存储机制,不与服务端发生通信。
3.IndexedDB用于客户端存储大量结构化的数据。
"你一边讨厌着你现在的生活状态,一边懒散地混过每一天。"