第25章 客户端存储

Web应用程序的出现,直接在客户端存储用户信息的需求也随之出现,无论是登录信息、个人偏好还是其它数据,与特定用户相关的信息应该保存在用户的机器上。

最初用于在客户端存储会话信息。cookie要求服务器在响应HTTP请求时,通过发送Set-Cookie HTTP 头部包含会话信息

HTTP/1.1 200 OK 
Content-type: text/html 
// 设置了一个名为name,值为value的cookie,名和值在发送时都会经过URL编码
Set-Cookie: name=value 
Other-header: other-header-value

// 浏览器会存储这些会话信息,并在之后的每个请求中都会通过 HTTP 头部 cookie 再将它们发回服务器
GET /index.jsl HTTP/1.1 
Cookie: name=value 
Other-header: other-header-value 

限制

cookie是与特定域绑定的,设置 cookie 后,它会与请求一起发送到创建它的域。这个限制能保证 cookie 中存储的信息只对被认可的接收者开放,不被其他域访问。

不超过 300 个 cookie,每个 cookie 不超过 4096 字节,每个域不超过 20 个 cookie,每个域能设置的 cookie 总数也是受限的,但不同浏览器的限制不同。

构成

name:唯一标识cookie的名称。不区分大小写

value:存储在cookie里的字符串值。这个值必须经过URL编码。

域domain:cookie有效的域。发送到这个域的所有请求都会包含对应的cookie。

path:请求URL中包含这个路径才会把cookie发送到服务器。

过期时间expires:表示何时删除cookie的时间戳

这些参数在 Set-Cookie 头部中使用分号加空格隔开:

HTTP/1.1 200 OK 
Content-type: text/html 
// 部设置一个名为"name"的 cookie,这个 cookie 在 2007 年 1 月 22 日 7:10:24 过期,对www.wrox.com 及其他 wrox.com 的子域(如 p2p.wrox.com)有效。
// 安全标志 secure 是 cookie 中唯一的非名/值对,只需一个 secure 就可以了
Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com ; secure
Other-header: other-header-value

安全标志secure:设置之后,只在使用SSL安全连接的情况下才会把cookie发送到服务器。 域、路径、过期时间和secure标志用于告诉浏览器什么情况下应该在请求中包含cookie。这些参数并不会随请求发送给服务器,实际发送的只有cookie的名/值对。

JavaScript中的cookie

document.cookie 返回包含页面中所有 有效 cookie 的字符串(根据域、路径、过期时间和安全设置),以分号分隔

name1=value1;name2=value2;name3=value3 

document.cookie = “name = XXX”就可以设置,但是最好还是使用encodeURICompnent()对名称和值进行编码

document.cookie = encodeURIComponent("name") + "=" + encodeURIComponent("Nicholas");

因为在 JavaScript 中读写 cookie 不是很直观,所以可以通过辅助函数来简化相应的操作。与 cookie 相关的基本操作有读、写和删除。这些在 CookieUtil 对象中表示

// 设置 cookie 
CookieUtil.set("name", "Nicholas"); 
CookieUtil.set("book", "Professional JavaScript"); 
// 读取 cookie 
alert(CookieUtil.get("name")); // "Nicholas" 
alert(CookieUtil.get("book")); // "Professional JavaScript" 
// 删除 cookie 
CookieUtil.unset("name"); 
CookieUtil.unset("book"); 
// 设置有路径、域和过期时间的 cookie 
CookieUtil.set("name", "Nicholas", "/books/projs/", "www.wrox.com", 
 new Date("January 1, 2010")); 
// 删除刚刚设置的 cookie 
CookieUtil.unset("name", "/books/projs/", "www.wrox.com"); 
// 设置安全 cookie 
CookieUtil.set("name", "Nicholas", null, null, null, true); 

Web storage

WebStorage的目的是解决通过客户端存储不需要频繁发送回服务器的数据时使用cookie的问题

WebStorage定义了两个对象:localStorage和sessionStorage。localStorage是永久存储机制,sessionStorage是跨会话的存储机制。

storage类型

Storage类型用于保存名/值对数据,有以下方法:

clear():删除所有值;不在Firefox中实现

getItem(name):取得给定name的值

key(index):取得给定数值位置的名称

removeItem(name):删除给定name的名/值对

setItem(name, value):设置给定name的值。 Storage类型只能存储字符串。非字符串数据在存储之前会自动转换为字符串

SessionStorage

sessionStorage对象只存储会话数据,这意味着数据只会存储到浏览器关闭。这跟浏览器关闭时 会消失的会话 cookie 类似。

因为 sessionStorage 对象是 Storage 的实例,所以可以通过使用 setItem()方法或直接给属 性赋值给它添加数据。

// 使用方法存储数据
sessionStorage.setItem("name", "Nicholas"); 
// 使用属性存储数据
sessionStorage.book = "Professional JavaScript"; 

// 使用方法取得数据
let name = sessionStorage.getItem("name"); 
// 使用属性取得数据
let book = sessionStorage.book; 

// 可以结合 sessionStorage 的 length 属性和 key()方法遍历所有的值
for (int i = 0,len = sessionStorage.length;i<len;i++) {
    let key = sessionStorage.key(i);
    let value = sessionStorage.getItem(key);
    alert(`${key} = ${value} `);
}
// 也可以使用 for-in 循环迭代 sessionStorage 的值
for (let key in sessionStorage) {
    let value = sessionStorage.getItem(key);
    alert(`${key} = ${value} `);
}

// 使用 delete 删除值
delete sessionStorage.name; 
// 使用方法删除值
sessionStorage.removeItem("book"); 

sessionStorage 对象应该主要用于存储只在会话期间有效的小块数据。如果需要跨会话持久存储 数据,可以使用localStorage.

LocalStorage

要访问同一个localStorage对象,页面必须来自同一个域(子域不可以)、在相同的端口上使用相同的协议。

// 使用方法存储数据
localStorage.setItem("name", "Nicholas"); 
// 使用属性存储数据
localStorage.book = "Professional JavaScript"; 
// 使用方法取得数据
let name = localStorage.getItem("name"); 
// 使用属性取得数据
let book = localStorage.book;

它与sessionStorage的区别就是,存储在 localStorage 中的数据会保留到通过 JavaScript 删除或者用户清除浏览器缓存。localStorage 数据不受页面刷新影响,也不会因关闭窗口、标签页或重新启动浏览 器而丢失。

存储事件

每当Storage对象发生变化时,都会在文档上触发storage事件。

这个事件的 事件对象有如下 4 个属性。

domain:存储变化对应的域。

key:被设置或删除的键。

newValue:键被设置的新值,若键被删除则为 null。

oldValue:键变化之前的值。

// 监听storage事件
window.addEventListener("storage", 
 (event) => alert('Storage changed for $&#123;event.domain&#125;'));

限制

客户端数据的大小限制是按照每个源(协议、域和端口)来设置的,因此每个源有固定大小的数 据存储空间。

不同浏览器给 localStorage 和 sessionStorage 设置了不同的空间限制,但大多数会限制为每 个源 5MB。

IndexedDB

Indexed Database API简称IndexedDB,是浏览器中存储结构化数据的一个方案。绝大多数 IndexedDB 操作要求添加 onerror 和 onsuccess 事件处理程序来确 定输出。

数据库

IndexedDB 是类似于 MySQL 或 Web SQL Database 的数据库。IndexedDB使用对象存储而不是表格保存数据。

1、打开数据库,建立数据库连接

let db, request, version = 1; 
 // 1参数:数据库名称 2参数:指定版本号
request = indexedDB.open("admin", version); 
request.onerror = (event) => 
 // 如果打开数据库期间发生了错误,event.target.errorCode中就会存储表示问题的错误码
 alert(`Failed to open: $&#123;event.target.errorCode&#125;`); 
request.onsuccess = (event) => &#123; 
    // 如果成功的话可以通过 event.target.result访问数据库实例了,这个实例会被保存在db中
 db = event.target.result; 
&#125;; 

2、使用对象存储

创建对象存储前,思考需要存储什么类型的数据

let user = &#123; 
// 适合作为对象存储键 全局唯一
 username: "007", 
 firstName: "James", 
 lastName: "Bond", 
 password: "foo" 
&#125;;

3、事务完成其它操作

事务要通过调用数据库对象的 transaction()方法创建。

// 简易创建事务方法
let transaction = db.transaction();
// 指定一个或多个要访问的对象存储的名称 以确保在事务期间只加载 users 对象存储的信息
let transaction = db.transaction("users"); 
// 访问多个对象存储,可以给第一个参数传入一个字符串数组
let transaction = db.transaction(["users", "anotherStore"]); 
// 每个事务都以只读方式访问数据。要修改访问模式,可以传入第二个参数。"readonly"、"readwrite"或"versionchange"
let transaction = db.transaction("users", "readwrite");