熟悉的web存储主要有以下几种:
- Cookie
- Web storage API
- IndexedDB
- Web Sql
Cookie:
概述:
- 服务器保存在浏览器的一小段文本信息,一般大小不能超过4KB。浏览器每次向服务器发出请求,就会自动附上这段信息。
- HTTP 协议不带有状态,有些请求需要区分状态,就通过 Cookie 附带字符串,让服务器返回不一样的回应。
- Cookie 不是一种理想的客户端存储机制。它的容量很小(4KB),缺乏数据操作接口,而且会影响性能。客户端存储建议使用 Web storage API 和 IndexedDB。只有那些每次请求都需要让服务器知道的信息,才应该放在 Cookie 里面
属性:
- Expires属性:指定一个具体的到期时间,到了指定时间以后,浏览器就不再保留这个 Cookie。它的值是 UTC 格式,可以使用Date.prototype.toUTCString()进行格式转换。
如果不设置该属性,或者设为null,Cookie 只在当前会话(session)有效,浏览器窗口一旦关闭,当前 Session 结束,该 Cookie 就会被删除。 - Max-Age属性:指定从现在开始 Cookie 存在的秒数,比如60 * 60 * 24 * 365(即一年)。过了这个时间以后,浏览器就不再保留这个 Cookie。
两者使用要点:
如果同时指定了Expires和Max-Age,那么Max-Age的值将优先生效。
如果Set-Cookie字段没有指定Expires或Max-Age属性,那么这个 Cookie 就是 Session Cookie,即它只在本次对话存在,一旦用户关闭浏览器,浏览器就不会再保留这个 Cookie。 - Domain属性:指定 Cookie 属于哪个域名,以后浏览器向服务器发送 HTTP 请求时,通过这个属性判断是否要附带某个 Cookie。未设置,默认将其设为浏览器的当前域名。
使用要点:
Domain 属性只能是当前域名或者当前域名的上级域名,但设为上级域名时,不能设为顶级域名或公共域名。(顶级域名指的是 .com、.net 这样的域名,公共域名指的是开放给外部用户设置子域名的域名,比如 github.io。)如果不符合上面这条规则,浏览器会拒绝设置这个 Cookie。 - Path属性:指定浏览器发出 HTTP 请求时,哪些路径要附带这个 Cookie。只要浏览器发现,Path属性是 HTTP 请求路径的开头一部分,就会在头信息里面带上这个 Cookie。比如,Path属性是/,那么请求/docs路径也会包含该 Cookie。当然,前提是 Domain 属性必须符合条件。
- Secure属性:指定浏览器只有在加密协议 HTTPS 下,才能将这个 Cookie 发送到服务器。另一方面,如果当前协议是 HTTP,浏览器会自动忽略服务器发来的Secure属性。该属性只是一个开关,不需要指定值。如果通信是 HTTPS 协议,该开关自动打开。
- HttpOnly属性:指定该 Cookie 无法通过 JavaScript 脚本拿到,主要是document.cookie属性、XMLHttpRequest对象和 Request API 都拿不到该属性。这样就防止了该 Cookie 被脚本读到,只有浏览器发出 HTTP 请求时,才会带上该 Cookie。
- SameSite属性:Chrome 51 开始浏览器新增的,用来防止 CSRF 攻击和用户追踪。
(Cookie 往往用来存储用户的身份信息,恶意网站可以设法伪造带有正确 Cookie 的 HTTP 请求,这就是 CSRF 攻击)
设置三个值:- Strict :最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
- Lax:规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
- None:Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
浏览器端操作cookie:
document.cookie属性用于读写当前网页的 Cookie。读取的时候,它会返回当前网页的所有 Cookie,前提是该 Cookie 不能有HTTPOnly属性。
document.cookie // “foo=bar;baz=bar”
上面代码从document.cookie一次性读出两个 Cookie,它们之间使用分号分隔。必须手动还原,才能取出每一个 Cookie 的值。
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
console.log(cookies[i]);
}
// foo=bar
// baz=bar
document.cookie写入 Cookie 的例子如下。
document.cookie = 'fontSize=14; '
+ 'expires=' + someDate.toGMTString() + '; '
+ 'path=/subdirectory; '
+ 'domain=*.example.com';
Cookie 的属性一旦设置完成,就没有办法读取这些属性的值。
删除一个现存 Cookie 的唯一方法,是设置它的expires属性为一个过去的日期。
Web storage API
概述:
Web 存储需要更加的安全与快速. 数据不会被保存在服务器上,这些数据只用于用户请求网站数据上.它也可以存储大量的数据,而不影响网站的性能.
分为两个对象:localStorage(用于长久保存整个网站的数据,保存的数据没有过期时间,直到手动去除); sessionStorage(用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据)。
属性:
不管是 localStorage,还是 sessionStorage,可使用的API都相同,常用的有如下几个(以localStorage为例):
- 保存数据:localStorage.setItem(key,value);
- 读取数据:localStorage.getItem(key);
- 删除单个数据:localStorage.removeItem(key);
- 删除所有数据:localStorage.clear();
- 得到某个索引的key:localStorage.key(index);
- 返回保存的数据项个数 localStorage.length;
Storage事件:
window.addEventListener('storage', onStorageChange);
监听函数接受一个event实例对象作为参数。这个实例对象继承了 StorageEvent 接口,有几个特有的属性,都是只读属性。
- StorageEvent.key:字符串,表示发生变动的键名。如果 storage 事件是由clear()方法引起,该属性返回null。
- StorageEvent.newValue:字符串,表示新的键值。如果 storage 事件是由clear()方法或删除该键值对引发的,该属性返回null。
- StorageEvent.oldValue:字符串,表示旧的键值。如果该键值对是新增的,该属性返回null。
- StorageEvent.storageArea:对象,返回键值对所在的整个对象。也说是说,可以从这个属性上面拿到当前域名储存的所有键值对。
- StorageEvent.url:字符串,表示原始触发 storage 事件的那个网页的网址。
具体例子:
function onStorageChange(e) {
console.log(e.key);
}
window.addEventListener('storage', onStorageChange);
注意,该事件有一个很特别的地方,就是它不在导致数据变化的当前页面触发,而是在同一个域名的其他窗口触发。也就是说,如果浏览器只打开一个窗口,可能观察不到这个事件。比如同时打开多个窗口,当其中的一个窗口导致储存的数据发生改变时,只有在其他窗口才能观察到监听函数的执行。可以通过这种机制,实现多个窗口之间的通信。
IndexedDB
概述:
随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。
现有的浏览器数据储存方案,都不适合储存大量数据:Cookie 的大小不超过 4KB,且每次请求都会发送回服务器;LocalStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引。所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。
通俗地说,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
特点:
- 键值对储存。 IndexedDB 内部采用对象仓库(object store)存放数据。所有类型的数据都可以直接存入,包括 JavaScript 对象。对象仓库中,数据以“键值对”的形式保存,每一个数据记录都有对应的主键,主键是独一无二的,不能有重复,否则会抛出一个错误。
- 异步。 IndexedDB 操作时不会锁死浏览器,用户依然可以进行其他操作,这与 LocalStorage 形成对比,后者的操作是同步的。异步设计是为了防止大量数据的读写,拖慢网页的表现。
- 支持事务。 IndexedDB 支持事务(transaction),这意味着一系列操作步骤之中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
- 同源限制。 IndexedDB 受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
- 储存空间大。 IndexedDB 的储存空间比 LocalStorage 大得多,一般来说不少于 250MB,甚至没有上限。
- 支持二进制储存。 IndexedDB 不仅可以储存字符串,还可以储存二进制数据(ArrayBuffer 对象和 Blob 对象)。
基本概念
- 数据库:IDBDatabase 对象(数据库是一系列相关数据的容器。每个域名(严格的说,是协议 + 域名 + 端口)都可以新建任意多个数据库。)
注意:ndexedDB 数据库有版本的概念。同一个时刻,只能有一个版本的数据库存在。如果要修改数据库结构(新增或删除表、索引或者主键),只能通过升级数据库版本完成。 - 对象仓库:IDBObjectStore 对象(每个数据库包含若干个对象仓库(object store)。它类似于关系型数据库的表格。)
- 索引: IDBIndex 对象(为了加速数据的检索,可以在对象仓库里面,为不同的属性建立索引。)
- 事务: IDBTransaction 对象(数据记录的读写和删改,都要通过事务完成。事务对象提供error、abort和complete三个事件,用来监听操作结果。)
- 操作请求:IDBRequest 对象
- 指针: IDBCursor 对象
- 主键集合:IDBKeyRange 对象
- 数据记录(对象仓库保存的是数据记录。每条记录类似于关系型数据库的行,但是只有主键和数据体两部分。数据体可以是任意数据类型,不限于对象。主键用来建立默认的索引,必须是不同的,否则会报错。主键可以是数据记录里面的一个属性,也可以指定为一个递增的整数编号。)
操作流程
- 打开数据库
//open方法这个方法接受两个参数,第一个参数是字符串,表示数据库的名字。如果指定的数据库不存在,就会新建数据库。第二个参数是整数,表示数据库的版本。如果省略,打开已有数据库时,默认为当前版本;新建数据库时,默认为1
//indexedDB.open()方法返回一个 IDBRequest 对象。这个对象通过三种事件error、success、upgradeneeded,处理打开数据库的操作结果。
var request = indexedDB.open('test',1);
var db;
request .onsuccess=function(e){
console.log('打开成功')
db = request .result;//通过request对象的result属性拿到数据库对象
}
request .onerror=function(e){
console.log('打开失败')
}
request .onupgradeneeded=function(e){//如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件
console.log('gradeneeding');
db = event.target.result;//通过事件对象的target.result属性,拿到数据库实例。
}
- 新建数据库
新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。
request.onupgradeneeded = function (e) {//需要升级
console.log('gradeneeding');
db = e.target.result;
console.log(db);
console.log(db.objectStoreNames);
if (!db.objectStoreNames.contains('person')) {//判断表格是否存在
var objectStore = db.createObjectStore('person', { keypath: 'id' });//数据库新建成功以后,新增一张叫做person的表格,主键是id
objectStore.createIndex('name', 'name', { unique: false });
objectStore.createIndex('email', 'email', { unique: true });//IDBObject.createIndex()的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)。
}
//说明:主键(key)是默认建立索引的属性。比如,数据记录是{ id: 1, name: '张三' },那么id属性可以作为主键。主键也可以指定为下一层对象的属性,比如{ foo: { bar: 'baz' } }的foo.bar也可以指定为主键。
//var objectStore = db.createObjectStore('person',{ autoIncrement: true }); //如果数据记录里面没有合适作为主键的属性,指定主键为一个递增的整数。
}
- 新增数据
function add() {
//写入数据需要新建一个事务。新建时必须指定表格名称和操作模式(“只读”或“读写”)。
//新建事务以后,通过IDBTransaction.objectStore(name)方法,拿到 IDBObjectStore 对象,再通过表格对象的add()方法,向表格写入一条记录。
var trans = dbRes.transaction(['person'], 'readwrite').objectStore('person').add({ id: 1, name: '张三', age: 24, email: 'zhangsan@example.com' });
trans.onsuccess = function (event) {
console.log('数据写入成功');
};
trans.onerror = function (event) {
console.log('数据写入失败');
}
};
- 读取数据
```javascript
function read() {
var trans = dbRes.transaction(['person']).objectStore('person').get(1);//objectStore.get()方法用于读取数据,参数是主键的值。
trans.onerror = function (event) {
console.log('事务失败');
};
trans.onsuccess = function (event) {
if (trans.result) {
console.log('Name: ' + trans.result.name);
console.log('Age: ' + trans.result.age);
console.log('Email: ' + trans.result.email);
} else {
console.log('未获得数据记录');
}
};
}
- 读取数据
function readAll() {
var objectStore = dbRes.transaction(['person']).objectStore('person');
objectStore.openCursor.onsuccess = function (e) {//新建指针对象的openCursor()方法是一个异步操作,所以要监听success事件。
var cursor = e.target.result;
console.log(cursor)
if (cursor) {
console.log('Id: ' + cursor.key);
console.log('Name: ' + cursor.value.name);
console.log('Age: ' + cursor.value.age);
console.log('Email: ' + cursor.value.email);
cursor.continue();
} else {
console.log('没有更多数据了!');
}
}
}
- 更新数据(增加数据)
function update() {
var request = dbRes.transaction(['person'], 'readwrite')
.objectStore('person')
.put({ id: 2, name: '李四', age: 35, email: 'lisi@example.com' });
request.onsuccess = function (event) {
console.log('数据更新成功');
};
request.onerror = function (event) {
console.log('数据更新失败');
}
}
21.删除数据
function remove() {
var request = dbRes.transaction(['person'], 'readwrite')
.objectStore('person')
.delete(1);
request.onsuccess = function (event) {
console.log('数据删除成功');
};
}
22.通过索引找数据
function indexRead() {
var transaction = dbRes.transaction(['person'], 'readonly').objectStore('person').index('name').get('李四');
transaction.onsuccess = function (e) {
var result = e.target.result;
if (result) {
console.log('name李四', result);
// ...
} else {
// ...
}
}
}
indexedDB 对象
- indexedDB.open()方法用于打开数据库。这是一个异步操作,但是会立刻返回一个 IDBOpenDBRequest 对象。
var openRequest = window.indexedDB.open('test', 1);
上面代码表示,打开一个名为test、版本为1的数据库。如果该数据库不存在,则会新建该数据库。
open()方法的第一个参数是数据库名称,格式为字符串,不可省略;第二个参数是数据库版本,是一个大于0的正整数(0将报错),如果该参数大于当前版本,会触发数据库升级。第二个参数可省略,如果数据库已存在,将打开当前版本的数据库;如果数据库不存在,将创建该版本的数据库,默认版本为1。
打开数据库是异步操作,通过各种事件通知客户端。下面是有可能触发的4种事件。
success:打开成功。
error:打开失败。
upgradeneeded:第一次打开该数据库,或者数据库版本发生变化。
blocked:上一次的数据库连接还未关闭。
第一次打开数据库时,会先触发upgradeneeded事件,然后触发success事件。
根据不同的需要,对上面4种事件监听函数。
- indexedDB.deleteDatabase() 方法用于删除一个数据库,参数为数据库的名字。它会立刻返回一个IDBOpenDBRequest对象,然后对数据库执行异步删除。删除操作的结果会通过事件通知,IDBOpenDBRequest对象可以监听以下事件。
success:删除成功
error:删除报错
调用deleteDatabase()方法以后,当前数据库的其他已经打开的连接都会接收到versionchange事件。
注意,删除不存在的数据库并不会报错。
- indexedDB.cmp() 方法比较两个值是否为 indexedDB 的相同的主键。它返回一个整数,表示比较的结果:0表示相同,1表示第一个主键大于第二个主键,-1表示第一个主键小于第二个主键。
window.indexedDB.cmp(1, 2) // -1
window.indexedDB.cmp(1, true) // 报错
window.indexedDB.cmp({}, {}) // 报错
注意,这个方法不能用来比较任意的 JavaScript 值。如果参数是布尔值或对象,它会报错。
Web Sql
概述
Web Sql 属于关系型数据库,可以直接通过sql语句进行查询。
核心方法
openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。
transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。
executeSql:这个方法用于执行实际的 SQL 查询。
简单使用
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024);//(openDatabase(数据库名称,版本号,描述文本,数据库大小,创建回调),第五个参数,创建回调会在创建数据库后被调用。)
var e_id = 3, e_log = 'sweet';
db.transaction(function (tx) {
tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)');//创建名为LOGS数据库
tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "hello world")');//插入数据
tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "yiyayiya")');
tx.executeSql('INSERT INTO LOGS (id, log) VALUES (?, ?)', [e_id, e_log]);//动态插入数据
});
//遍历数据
db.transaction(function (tx) {
tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) {
var len = results.rows.length, i;
console.log('共有数据' + len);
for (i = 0; i < len; i++) {
console.log(results.rows.item(i).log);
}
}, null);
});