本系列其他译文请看[JS工作机制 - 小白1991的专栏 - 掘金 (juejin.cn)] (juejin.cn/column/6988…
本章阅读指数:3
本地存储使用的场景不多,但是如果做可视化,或者大量数据展示可能会用到本地存储来缓存,以便提升性能。可以视情况阅读。
概述
设计应用时选择合适的存储机制是很重要的。一个好的存储引擎,应该确保数据稳定,减少带宽,提升性能。正确的存储缓存策略,是构建离线手机应用的核心之一,越来越多的用户想当然以为可以离线使用移动端网页程序。
这一章,我们要讨论存储API和服务,照例提供一下引导帮助你在构建应用时做出正确的选择。
数据模型
数据的存储模型取决于数据内部的组织方式。这影响了整个应用的设计,要权衡性能和存储带来的问题。没有所谓更好,或者能解决所有问题的方案,一切都依赖于开发者自己。因此,我们先看看我们可以选择哪些数据模型
- 结构型: 数据按照预定义的字段表来村存储,就像典型的SQL类数据库,他们具有很好的适配性,也便于动态查询。浏览器中的结构化存储是IndexedDB。
- 键值对: 键值对数据存储,类似于NoSQL数据库,通过唯一的键来存储和获取非结构化的数据。键值对数据存储,类似于哈希表,这样在访问索引时花费的时间是固定的。键/值数据型存储的很好的例子有浏览器端的 Cache API 和 服务器端 Apache Cassandra。
- 字节流: 这种简单的模型,把数据存储为定长,混淆字符串的字节变量,让应用层来控制其内部数据组织。该模型尤其适合于文件存储和其它层次型组织的 blob 数据。字节流存储的典型例子包括文件系统和云存储设备。
持久化
网页程序的数据存储方法可以以数据的存储时长来进行分析:
- Session 持久化: 这种数据只要一个网页的session或者浏览器tab激活时持久化。 session持久性数据存储一个例子即 Session Storage API。
- 设备持久化: 这一类数据的持久化,是在一部分设备商跨session和浏览器tab的。一个设备持久化的存储的机制是Cache API.
- 全局持久化: 这一类数据持久化是跨session和设备的。因此,这是稳定的数据持久化方式。它不能在设备本身上存储,因此你需要一些服务端存储。
浏览器中的数据持久化
现在有了一些浏览器的API支持数据存储。我们挑一些看看,并且做一个简单的比较,以便于你做出争取的选择。
在选择合适的方式之前,要考虑一些事情。当然,首先你要先理解你的应用如何使用,后去维护和增强的。即使你已经有了答案,还是要考虑以下几个问题:
- 浏览器支持 — 优先选择已经标准化的,并且稳定的API,因为它们已经存在很久并且应用很广泛。这些API也有丰富的文档和社区开发者。
- 事务 —有时,存储操作集成败的原子化是非常重要的。传统上上,数据库使用事务模型达到原子化。在事务模型中以把相关数据更新划分为任意的单元。
- 异步同步 — 有一些存储API是同步的,这样会阻塞当前线程。因此尽可能使用异步的。
对比
看一下当前web开发者使用的API以及简单的横向对比一下
文件系统 API
使用文件系统的API,网络应用可以在用户本地文件系统的沙箱中创建,阅读,导航以及写文件。
API分为几个部分:
- 读和操作文件:
File/Blob
,FileList
,FileReader
- 创建和写文件:
Blob()
,FileWriter
- 目录和文件访问:
DirectoryReader
,FileEntry/DirectoryEntry
,LocalFileSystem
文件系统API是不标准的API。你不能在发布的程序中使用,因为它不是对所有用户生效。各个实现之间是不兼容的,比并且API可能会改变。
文件和目录 API 接口文件系统用来表示一个文件系统。可从任意文件系统条目的 filesystem 属性中获取这些 对象。少数浏览器提供了额外的 API 来创建和操作文件系统。
该接口不会允许开发者访问用户的文件系统。相反,开发者会在浏览器沙箱内获得一个虚拟磁盘。若想要访问用户的文件系统,可以采取安装 Chrome 插件的方法。
请求一个文件系统
调用 window.requestFileSystem()
,网络应用可以访问沙箱文件系统:
如果你第一次调用requestFileSystem(),会为你的应用创建新的存储。谨记,这个文件系统是沙箱,你的应用是无法访问其他的应用文件的。
访问文件系统之后,你可以在文件和目录上执行大部分的标准行为。
相比其他,文件系统是一个不同的存储方式,它旨在满足客户端存储不能满足的场景。一般来说,他们被用来处理大型二进制 blobs 文件或者在浏览器上下文之外的程序中分享数据。
以下为使用 FileSystem 的好范例:
- 持久化上传--选择一个文件或者目录去上传,首先会拷贝文件到本地的沙箱,然后一次上传一个分片
- 视频游戏,音乐或者其他的有很多媒体资源的应用
- 离线的媒体/图片编辑器或者缓存加速的--数据块很大而且需要读写
- 离线媒体播放器-它需要下载大文件,稍后播放或者快速的寻轨-缓冲
- 离线网络邮件客户端---下载附件并本地存储
看看浏览器的支持情况:
本地存储
localStorage
API允许你访问文档源的Storage 对象。这个存储是跨浏览器session的。本地存储很类似sessionStorage,除了localStorage中保存的数据没有过期时间,一旦页面关闭,sessionStorage中存储的数据就会被清理掉。
无论是 localStorage 还是 sessionStorage 其数据只存储在特定的页面源中,所谓页面源包含协议,主机名和端口。 看一下浏览器支持情况
Session 存储
sessionStorage 属性允许你为当前的源获取sessionStorage对象。sessionStorage有点类似localStorage。不同的是,存储在 localStorage 中的数据没有过期时间,而 sessionStorage 中的数据会在页面会话结束时丢失。页面会话的时效为浏览器打开时且在页面重载和恢复时。在新的选项卡中打开新页面或者窗口会导致重新初始化一个新的会话,这与会话 cookies 的工作机制是不一样的。
同样,无论是 localStorage 还是 sessionStorage 其数据只存储在特定的页面源中
看一下浏览器支持情况
Cookies
cookie是用户服务器发送给用户浏览器的一小片数据。浏览器可能存储它,并在之后的请求中返回给相同的服务器。典型的,它可以用来表示两个请求是否是同一个浏览器,以便保持用户的登录态。它为无状态的HTTP协议保存了有用的状态信息。
使用cookies的主要场景:
- Session 管理 — 登录,购物车,游戏得分,或者服务器需要记住的其他信息
- 个人信息 — 用户的偏好,主题和其他个性设置。
- 跟踪 — 记录和分析用户行为
Cookies 一度是客户端存储的常用方式,这是因为当时只有这一种方式存储数据。现在推荐使用现代的存储APIS。Cookies包含在每一次请求中,所以会影响性能(特别是当在一个移动端请求数据的时候)。
有两种cookies:
- Session cookies — 当客户端关闭时会被删除。网页浏览器可以使用恢复会话技术来固化大多数会话 cookie,就好像未曾关闭浏览器一样。
- Permanent cookies — 和客户端关闭即过期相反,permanent cookies 会在指定的过期时间过期或者在一个指定的时间(Max-age)后过期。
注意,机密和敏感的信息不能保存,也不能在HTTP cookies中传输,因为cookie的机制本身就不安全。
cookies已经在浏览器中广泛支持了
缓存
缓存接口为缓存的Request / Response对象提供了一个存储机制。注意缓存接口是被暴露到窗口作用域,就像worker一样。虽然 Cache 是在服务工作线程规范中定义的,但这并不表示一定要和服务工作线程一起使用。
一个源可以拥有多个命名的缓存对象。开发者需要在脚本(比如在服务工作线程中)中实现如何处理更新缓存即可。除非显示请求否则不会更新缓存中的对象,只能通过删除缓存对象,否则不会过期。使用 CacheStorage.open() 来打开指定命名的缓存对象,然后调用任意的缓存方法来维护缓存。
开发者需要定时清除缓存条目。每个源在浏览器端都有限额的缓存数据。使用 StorageEstimate 来估算使缓存配额使用率。浏览器尽力管理硬盘空间,但它有可能会删除指定源的缓存数据。浏览器可能会删除指定源的所有数据抑或不会。切记使用名称来对脚本进行版本控制且只操作可以安全操作的脚本版本。查看 Deleting old caches 以获取更多信息。
CacheStorage 接口表示 Cache 对象存储。
接口:
- 给所有命名的缓存提供一个主目录,这个目录可以被ServiceWorker或者其他的worker访问
- 维持一个字符串名和缓存对象的映射
使用CacheStorage.open()创建一个Cache实例。
使用CacheStorage.match()检查给定的Request是否是CacheStorage 对象中的Cache对象的一个键。
通过全局的caches属性访问CacheStorage。
IndexedDB
IndexedDB可以让你在浏览器中持久化存储数据。它可以让你创建一个具有丰富的查询功能的应用,并且不用关心网络。无论是否有网,你的应用都能正常工作。IndexedDB适用于大量数据(比如DVD仓库的借阅),并且不需要稳定的网络就能工作的软件(比如邮件客户端,代办列表,笔记本等。
伴随网络应用变得复杂,IndexedDB 也越来越流行了。这一章我们就讨论一些细节。
初始化IndexedDB
IndexedDB 可以让使用'key'来存储和恢复保存的对象。数据库的所有改变都包含在事务当中,同样,indexedDB 尊徐同源策略。你只可以访问同域名下的数据。
IndexedDB是一个异步API,可以在大多数的上下文中使用,包括WebWorkers。当然它也有同步的版本,但是已经被取消了。
IndexedDB曾经有一个竞争伙伴,WebSQL,但是WebSQL已经被W3C放弃了。WebSQL数据库是一个关系型数据访问系统,而IndexedDB是一个索引表系统。
不要根据其他数据库的经验来使用IndexedDB。相反的,你需要好好看文档。有一些关键的概念需要谨记:
- IndexedDB 存储键值对 — 值可以是复杂结构的对象,键可以是这些对象的属性。你可以使对象中的任何属性创建索引,进行快速查找,比如排过序的枚举。键还可以是二进制对象。
- IndexedDB 基于事务数据模型 — IndexedDB中做的一切事情,都发生在事务上下文的内部。不能在事务之外执行命令或者打开一个游标。同理,事务自动提交,不能手动提交
- IndexedDB API 大多是异步的 —API不会返回数据给你。相反的,你需要传递回调函数进去。你不能用同步的方式,在数据库中存储或者恢复一个值。相反的,你需要请求数据库操作的执行,当执行完成了,会发送一个事件通知,这个事件的类型告诉开发者操作的成败。这个跟[XMLHttpRequest]操作很相似。
- IndexedDB需要很多request - request 对象接受前面提到的操作成功过着失败的事件。他们有onsuccess和onerror属性,和 readyState,result,errorCode 属性一样,用来告知请求状态。
- IndexedDB 是面向对象的 — IndexedDB不像传统的数据库,基于行列集。这种模式上的不同,影响我们构建应用的方式。
- IndexedDB 不使用SQL — 它使用索引查询,索引会生成一个游标,使用游标可以遍历你查询的结果集。若不熟悉 NoSQL 系统,可以阅读 维基百科关于 NoSQL 的文章。
- IndexedDB遵循同源策略 — 源就是域名,应用层协议,以及执行的脚本文档的url端口的合集。每一个源都有自己的数据库,每一个数据库都有一个名字表示所处的源。
IndexedDB 的局限性
IndexedDB 被设计用来满足大多数的客户端存储情况的。然而,它并没有被设计用来处理如下情况:
- 国际化排序 — 不同语言的字符串排序是不同的,因为不支持国际化的排序。因此,查询到的数据可以需要自己排一下序。
- 同步 — 这个API的设计没有考虑和服务端的同步。你需要自己写代码来处理客户端和服务端的数据同步。
- 全文搜索 — 没有类似于SQL中LIKE的操作
另外,在以下条件中,数据库会被清除
- 用户请求清除 — 很多浏览器是支持让用户擦除指定网址的数据的,包括cookie,书签,密码和IndexedDB数据。
- 浏览器是隐私模式 — 一些浏览器有隐私模式或者匿名模式,当用户关闭会话时,浏览器自动清除数据库。
- 磁盘容量慢了
- 数据损坏 .
The exact circumstances and browser capabilities change over time, but the general philosophy of the browser vendors is to make the best effort to keep the data when possible. 具体的环境和浏览器能力日新月异,但是浏览器产商都朝着尽一切可能保存数据的方向努力。
选择合适的存储API
前面讲过,最好选择广泛应用,具有异步能力的API。这自然引出了以下几个技术点:
- 对于离线存储,使用[Cache API]。所有支持Service Worker的浏览器都可以支持它。Cache API 非常适用于排列已知 URL 的关联资源。
- 使用IndexedDB 来存储应用状态和用户生成的内容。和只支持 Cache API 的浏览器相比,这使得用户可以在更多的浏览器中离线使用程序。