这是我参与更文挑战的第16天,活动详情查看: 更文挑战
这篇文章整理浏览器的存储知识,主要是 Cookie、localStorage、sessionStorage、indexDB 等存储方式的介绍。
Cookie
Cookie
是服务器委托浏览器存储的一些数据,它让服务器有了“记忆能力”,它会在浏览器下次向服务器再发起请求时被携带并发送到服务器上。
Cookie 的工作过程
会用到两个字段:响应头字段 Set-Cookie
和请求头字段 Cookie
。
- 响应报文使用
Set-Cookie
字段发送“key=value
”形式的Cookie
值 - 请求报文里用
Cookie
字段发送多个Cookie
值
Cookie 的属性
Cookie
的生存周期:可以使用 Expires
和 Max-Age
两个属性来设置
Expires
即过期时间,用的是绝对时间点,可以理解为“截止日期”(deadline
)。Max-Age
用的是相对时间,单位是秒,浏览器用收到报文的时间点再加上Max-Age
,就可以得到失效的绝对时间。Expires
和Max-Age
可以同时出现,浏览器会优先采用Max-Age
计算失效期。
Cookie
的作用域:让浏览器仅发送给特定的服务器和 URI
,避免被其他网站盗用。
Domain
和Path
指定了Cookie
所属的域名和路径,浏览器在发送Cookie
前会从URI
中提取出host
和path
部分,对比Cookie
的属性。- 如果不满足条件,就不会在请求头里发送
Cookie
。
Cookie
的安全性:尽量不要让服务器以外的人看到。
HttpOnly
:Cookie
只能通过浏览器 HTTP 协议传输,禁止其他方式访问,比如不能通过 JS 访问Cookie
,减少 XSS 攻击;SameSite
:SameSite
可以防范 XSRF(跨站请求伪造)攻击,设置成SameSite=Strict
可以严格限定Cookie
不能随着跳转链接跨站发送,而SameSite=Lax
则略宽松一点,允许GET
/HEAD
等安全方法,但禁止POST
跨站发送Secure
:表示这个Cookie
仅能用 HTTPS 协议加密传输,明文的 HTTP 协议会禁止发送。
Cookie 的应用
- 身份识别:
Cookie
最基本的一个用途就是身份识别,保存用户的登录信息,实现会话事务 - 广告跟踪:广告商网站(例如 Google),它会“偷偷地”给你贴上
Cookie
小纸条,这样你上其他的网站,别的广告就能用Cookie
读出你的身份,然后做行为分析,再推给你广告。
Cookie 的特点
- 数据生命周期:一般由服务器生成,可以设置过期时间
- 数据存储大小:
Cookie
的大小受限,一般为4 KB
- 与服务端通信:每次发起同域下的 HTTP 请求时,在
header
中都会携带当前域名下的Cookie
,会影响请求的性能
Cookie 使用示例
// 读取网站下所有的 cookie 信息,获取的结果是一个以分号`;`作为分割的一个字符串
let allCookies = document.cookie;
// 往原来的已经存在的 cookie 中加入新的 cookie
document.cookie ="name=vincent";
// 还可以在后面加上可选择的选项键值对,如 domain、path、expires
document.cookie="name=vincent;domain=.test.com"
// 删除cookie,就是让这个 cookie 的 expires 过期,即设置这个 expires 的值为 0
document.cookie="name=vincent;domain=.test.com;expires=0");
复制代码
localStorage
这是一种持久化的存储方式,即说如果不手动清除,数据就永远不会过期。它是采用键值对的方式存储数据,按域名将数据分别保存到对应数据库文件里。相比 Cookie
来说,它能保存更大的数据。
localStorage 的特点
- 数据生命周期:除非被清理,否则会一直存在
- 数据存储大小:5M
- 与服务端通信:不与服务端进行通信
localStorage 使用示例
// setItem() 设置属性
localStorage.setItem('name', 'vincent');
// getItem() 获取数据
let name = localStorage.getItem('name');
// removeItem() 移除某个属性
localStorage.removeItem('name');
// clear() 移除所有数据项
localStorage.clear();
复制代码
模拟实现一个 localStorage
class myLocalStorage {
constructor() {
this.store = {};
}
setItem(key, val) {
this.store[key] = val;
}
getItem(key) {
return this.store[key] || null;
}
removeItem(key) {
delete this.store[key]
}
clear() {
this.store = {};
}
}
复制代码
实现过期时间功能
(function () {
let getItem = localStorage.getItem.bind(localStorage)
let setItem = localStorage.setItem.bind(localStorage)
let removeItem = localStorage.removeItem.bind(localStorage)
localStorage.getItem = function (keyName) {
let expires = getItem(keyName + '_expires')
if (expires && new Date() > new Date(Number(expires))) {
removeItem(keyName)
removeItem(keyName + '_expires')
}
return getItem(keyName)
}
localStorage.setItem = function (keyName, keyValue, expires) {
if (typeof expires !== 'undefined') {
let expiresDate = new Date(expires).valueOf()
setItem(keyName + '_expires', expiresDate)
}
return setItem(keyName, keyValue)
}
})()
复制代码
sessionStorage
sessionStorage
是一种会话级别的缓存,关闭浏览器时数据会被清除。
需要注意的是 sessionStorage
的作用域是窗口级别的,也就是说不同窗口之间保存的 sessionStorage
数据是不能共享的。
sessionStorage 的特点
- 数据生命周期:数据在页面刷新后依然存在,但是关闭浏览器标签页之后数据就会被清除
- 数据存储大小:5M
- 与服务端通信:不与服务端进行通信
sessionStorage 使用示例
sessionStorage
与 localStorage
拥有统一的 API 接口:
// setItem() 设置属性
sessionStorage.setItem('name', 'vincent');
// getItem() 获取数据
let name = sessionStorage.getItem('name');
// removeItem() 移除某个属性
sessionStorage.removeItem('name');
// clear() 移除所有数据项
sessionStorage.clear();
复制代码
indexDB
indexDB
是 NoSQL 数据库,是一种支持事务的浏览器数据库,用于客户端存储大量结构化数据,包括文件、二进制大型对象。
indexDB 的特点
- 数据生命周期:除非被清理,否则会一直存在
- 数据存储大小:无限制
- 与服务端通信:不与服务端进行通信
indexDB 使用示例
// 使用 indexedDB.open() 方法创建或打开数据库
var request = window.indexedDB.open(dbName, version); // 数据库名 版本号
// error 事件表示打开数据库失败
request.onerror = function (event) {
console.log('创建或打开数据库失败');
};
// success 事件表示成功打开数据库
var db;
request.onsuccess = function (event) {
db = request.result;
console.log('创建或打开数据库成功');
};
复制代码
下面是我之前一个项目用到的 indexDB
的一些代码:
/**
1、数据库名(mydb)
2、版本号(1.0)
3、描述(Test DB)
4、数据库大小(2*1024*1024)
*/
var sqliteDB = function (db_name, size) {
var _db = openDatabase(db_name, '1.0.0', '', size); // 生成或打开数据库
var currSql = null;
return {
/**
* 执行sql,回调返回影响条数
*/
execute: function (sql, param, callback) {
currSql = sql;
// 参数处理
if (!param) {
param = [];
} else if (typeof param == 'function') {
callback = param;
param = [];
}
this.query(sql, param, function (result) {
if (typeof callback == 'function') {
callback(result.rowsAffected);
}
});
},
/**
* 执行sql,回调返回sql查询对象
* 查询时,有数据返回数组,无数据返回0
* 增删改时:返回int,影响条数
* void query( string[, function])
* void query( string[, array[, function]])
*/
query: function (sql, param, callback) {
currSql = sql;
// 参数处理
if (!param) {
param = [];
} else if (typeof param == 'function') {
callback = param;
param = [];
}
var self = this;
// 只有一个参数
_db.transaction(function (tx) {
// 4个参数:sql,替换sql中问号的数组,成功回调,出错回调
tx.executeSql(sql, param, function (tx, result) {
if (typeof callback == 'function') {
callback(result);
}
}, self.onfail);
}, self.onTransactionFail);
},
executeSql: function (sql, param, callback) {
currSql = sql;
// _db.transaction(fn, success, error);
//参数处理
if (!param) {
param = [];
} else if (typeof param == 'function') {
callback = param;
param = [];
}
var self = this;
// 只有一个参数
_db.transaction(function (tx) {
// 4个参数:sql,替换sql中问号的数组,成功回调,出错回调
tx.executeSql(sql, param, function (tx, result) {
if (typeof callback == 'function') {
callback(result);
}
}, self.onfail);
}, self.onTransactionFail);
// console.log(sql);
},
/**
* 插入,回调返回last id
* void insert( string, object[, function])
*/
insert: function (table, data, callback) {
if (typeof data != 'object' && typeof callback == 'function') {
callback(0);
}
var k = [];
var v = [];
var param = [];
for (var i in data) {
k.push(i);
v.push('?');
param.push(data[i]);
}
var sql = "INSERT INTO " + table + "(" + k.join(',') + ") VALUES(" + v.join(',') + ")";
currSql = sql;
this.query(sql, param, function (result) {
if (typeof callback == 'function') {
callback(result.insertId);
}
});
},
/**
* 修改,回调返回影响条数
* void update( string, object[, string[, function]])
* void update( string, object[, string[, array[, function]]])
*/
update: function (table, data, where, param, callback) {
// 参数处理
if (!param) {
param = [];
} else if (typeof param == 'function') {
callback = param;
param = [];
}
var set_info = this.mkWhere(data);
for (var i = set_info.param.length - 1; i >= 0; i--) {
param.unshift(set_info.param[i]);
}
var sql = "UPDATE " + table + " SET " + set_info.sql;
if (where) {
sql += " WHERE " + where;
}
currSql = sql;
this.query(sql, param, function (result) {
if (typeof callback == 'function') {
callback(result.rowsAffected);
}
});
},
/**
* 删除
* void toDelete( string, string[, function]])
* void toDelete( string, string[, array[, function]])
*/
del: function (table, where, param, callback) {
// 参数处理
if (!param) {
param = [];
} else if (typeof param == 'function') {
callback = param;
param = [];
}
var sql = "DELETE FROM " + table + " WHERE " + where;
currSql = sql;
this.query(sql, param, function (result) {
if (typeof callback == 'function') {
callback(result.rowsAffected);
}
});
},
/***
* 事务
*/
transaction: function (fn, success, error) {
var hookError = function (tx, e) {
console.log(tx.message);
if (error) error(tx, e);
};
_db.transaction(fn, hookError, success);
//_db.transaction(fn, error,success);
},
/**
* 查询,回调返回结果集数组
* void fetch_all( string[, function] )
* void fetch_all( string[, param[, function]] )
*/
fetchAll: function (sql, param, callback) {
currSql = sql;
// 参数处理
if (!param) {
param = [];
} else if (typeof param == 'function') {
callback = param;
param = [];
}
this.query(sql, param, function (result) {
if (typeof callback == 'function') {
var out = [];
if (result.rows.length) {
for (var i = 0; i < result.rows.length; i++) {
out.push(result.rows.item(i));
}
}
callback(out);
}
});
},
/**
* 查询表的信息
* table_name: 表名称,支持 % *,
*/
showTables: function (table_name, callback) {
this.fetchAll("select * from sqlite_master where type='table' and name like ?", [table_name], callback);
},
/**
* 组装查询条件
*/
mkWhere: function (data) {
var arr = [];
var param = [];
if (typeof data === 'object') {
for (var i in data) {
arr.push(i + "=?");
param.push(data[i]);
//console.log('data.i:'+i);
}
}
return {
sql: arr.join(' , '),
param: param
};
},
onTransactionFail: function (tx, e) {
showSqliteError(tx);
},
/**
* 错误处理
*/
onfail: function (tx, e) {
console.log('sql error: ' + e.message);
console.log('执行sql:' + currSql);
}
};
};
// 调用 initDataBase() 生成或打开数据库
function initDataBase() {
fgDB = new sqliteDB(dbName, 1024 * 1024 * 2);
initFGdb();
}
// 数据库表的内容
function initFGdb() {
fgDB.transaction(function (tx) {
// 用户表
tx.executeSql('CREATE TABLE IF NOT EXISTS [user] (id VARCHAR2(32) PRIMARY KEY NOT NULL, name VARCHAR2(32), password VARCHAR2(32),phone VARCHAR2(32), remark VARCHAR2(32), define VARCHAR2(32))');
}, function () {
console.log('创建数据库成功!');
}, function (err) {
console.log('创建数据库失败!');
console.log(err);
});
}
复制代码
总结
Cookie
可以设置过期时间;存储大小一般为4KB
;会被携带在header
上与服务端通信localStorage
需要主动清理,否则会一直存在;存储大小为5M
;不参与服务端通信sessionStorage
和localStorage
类似,区别在于存储在页面关闭之后就清除indexDB
需要主动清理,否则会一直存在;存储大小没有限制;不参与服务端通信
存储的使用:现在已经不建议使用 Cookie
来存储数据,当没有大量数据存储需求可以用 localStorage
和 sessionStorage
,不怎么改变的数据用 localStorage
,否则的话用 sessionStorage
;而当有大量数据存储需求时可以使用 indexDB
。