浏览器的本地存储主要分为CookieWebStorageIndexDB, 其中WebStorage又可以分为localStoragesessionStorage。接下来我们就来一一分析这些本地存储方案。

1.Cookie

Cookie 最开始被设计出来其实并不是来做本地存储的,而是为了弥补HTTP状态管理上的不足
HTTP 协议是一个无状态协议,客户端向服务器发请求,服务器返回响应,故事就这样结束了,但是下次发请求如何让服务端知道客户端是谁呢?
这种背景下,就产生了 Cookie.
Cookie 本质上就是浏览器里面存储的一个很小的文本文件,内部以键值对的方式来存储(在chrome开发者面板的Application这一栏可以看到)。向同一个域名下发送请求,都会携带相同的 Cookie,服务器拿到 Cookie 进行解析,便能拿到客户端的状态。
Cookie 的作用很好理解,就是用来做状态存储的,但它也是有诸多致命的缺陷的:

  1. 容量缺陷。Cookie 的体积上限只有4KB,只能用来存储少量的信息。
  2. 性能缺陷。Cookie 紧跟域名,不管域名下面的某一个地址需不需要这个 Cookie ,请求都会携带上完整的 Cookie,这样随着请求数的增多,其实会造成巨大的性能浪费的,因为请求携带了很多不必要的内容。
  3. 安全缺陷。由于 Cookie 以纯文本的形式在浏览器和服务器中传递,很容易被非法用户截获,然后进行一系列的篡改,在 Cookie 的有效期内重新发送给服务器,这是相当危险的。另外,在HttpOnly为 false 的情况下,Cookie 信息能直接通过 JS 脚本来读取。

    2.localStorage

    2.1 和Cookie异同

    localStorage有一点跟Cookie一样,就是针对一个域名,即在同一个域名下,会存储相同的一段localStorage
    不过它相对Cookie还是有相当多的区别的:

  4. 容量。localStorage 的容量上限为5M,相比于Cookie的 4K 大大增加。当然这个 5M 是针对一个域名的,因此对于一个域名是持久存储的。

  5. 只存在客户端,默认不参与与服务端的通信。这样就很好地避免了 Cookie 带来的性能问题安全问题
  6. 接口封装。通过localStorage暴露在全局,并通过它的 setItemgetItem等方法进行操作,非常方便。

    2.2 操作方式

    接下来我们来具体看看如何来操作localStorage
    1. let obj = { name: "sanyuan", age: 18 };
    2. localStorage.setItem("name", "sanyuan");
    3. localStorage.setItem("info", JSON.stringify(obj));
    接着进入相同的域名时就能拿到相应的值:
    1. let name = localStorage.getItem("name");
    2. let info = JSON.parse(localStorage.getItem("info"));
    从这里可以看出,localStorage其实存储的都是字符串,如果是存储对象需要调用JSONstringify方法,并且用JSON.parse来解析成对象。

    2.3 应用场景

    利用localStorage的较大容量和持久特性,可以利用localStorage存储一些内容稳定的资源,比如官网的logo,存储Base64格式的图片资源,因此利用localStorage

    3sessionStorage

    3.1 特点

    sessionStorage以下方面和localStorage一致:
  • 容量。容量上限也为 5M。
  • 只存在客户端,默认不参与与服务端的通信。
  • 接口封装。除了sessionStorage名字有所变化,存储方式、操作方式均和localStorage一样。

sessionStoragelocalStorage有一个本质的区别,那就是前者只是会话级别的存储,并不是持久化存储。会话结束,也就是页面关闭,这部分sessionStorage就不复存在了。

3.2 应用场景

  1. 可以用它对表单信息进行维护,将表单信息存储在里面,可以保证页面即使刷新也不会让之前的表单信息丢失。
  2. 可以用它存储本次浏览记录。如果关闭页面后不需要这些记录,用sessionStorage就再合适不过了。事实上微博就采取了这样的存储方式。

    4.IndexDB

    IndexDB是运行在浏览器中的非关系型数据库, 本质上是数据库,绝不是和刚才WebStorage的 5M 一个量级,理论上这个容量是没有上限的。
    关于它的使用,本文侧重原理,而且 MDN 上的教程文档已经非常详尽,这里就不做赘述了,感兴趣可以看一下使用文档
    接着我们来分析一下IndexDB的一些重要特性,除了拥有数据库本身的特性,比如支持事务存储二进制数据,还有这样一些特性需要格外注意:

  3. 键值对存储。内部采用对象仓库存放数据,在这个对象仓库中数据采用键值对的方式来存储。

  4. 异步操作。数据库的读写属于 I/O 操作, 浏览器中对异步 I/O 提供了支持。
  5. 受同源策略限制,即无法访问跨域的数据库。

    5.Service Worker

    Service Worker 是运行在浏览器背后的独立线程,一般可以用来实现缓存功能。使用 Service Worker的话,传输协议必须为 HTTPS。因为 Service Worker 中涉及到请求拦截,所以必须使用 HTTPS 协议来保障安全。
    Service Worker 实现缓存功能一般分为三个步骤:首先需要先注册 Service Worker,然后监听到 install 事件以后就可以缓存需要的文件,那么在下次用户访问的时候就可以通过拦截请求的方式查询是否存在缓存,存在缓存的话就可以直接读取缓存文件,否则就去请求数据。以下是这个步骤的实现:

    1. // index.js
    2. if (navigator.serviceWorker) {
    3. navigator.serviceWorker
    4. .register('sw.js')
    5. .then(function(registration) {
    6. console.log('service worker 注册成功')
    7. })
    8. .catch(function(err) {
    9. console.log('servcie worker 注册失败')
    10. })
    11. }
    12. // sw.js
    13. // 监听 `install` 事件,回调中缓存所需文件
    14. self.addEventListener('install', e => {
    15. e.waitUntil(
    16. caches.open('my-cache').then(function(cache) {
    17. return cache.addAll(['./index.html', './index.js'])
    18. })
    19. )
    20. })
    21. // 拦截所有请求事件
    22. // 如果缓存中已经有请求的数据就直接用缓存,否则去请求数据
    23. self.addEventListener('fetch', e => {
    24. e.respondWith(
    25. caches.match(e.request).then(function(response) {
    26. if (response) {
    27. return response
    28. }
    29. console.log('fetch source')
    30. })
    31. )
    32. })

    打开页面,可以在开发者工具中的 Application 看到 Service Worker 已经启动了
    image.png
    在 Cache 中也可以发现我们所需的文件已被缓存
    image.png
    当我们重新刷新页面可以发现我们缓存的数据是从 Service Worker 中读取的
    image.png

    5.总结

    浏览器中各种本地存储和缓存技术的发展,给前端应用带来了大量的机会,PWA 也正是依托了这些优秀的存储方案才得以发展起来。重新梳理一下这些本地存储方案:

  6. cookie并不适合存储,而且存在非常多的缺陷。

  7. Web Storage包括localStoragesessionStorage, 默认不会参与和服务器的通信。
  8. IndexDB为运行在浏览器上的非关系型数据库,为大型数据的存储提供了接口。