1. cookie
1.1 什么是cookie
HTTP协议本身是无状态的。什么是无状态呢,即服务器无法判断用户身份。Cookie实际上是一小段的文本信息(key-value格式)。客户端向服务器发起请求,如果服务器需要记录该用户状态,就使用response向客户端浏览器颁发一个Cookie。客户端浏览器会把Cookie保存起来。当浏览器再请求该网站时,浏览器把请求的网址连同该Cookie一同提交给服务器。服务器检查该Cookie,以此来辨认用户状态,常用于存放token信息。
打个比方,我们去银行办理储蓄业务,第一次给你办了张银行卡,里面存放了身份证、密码、手机等个人信息。当你下次再来这个银行时,银行机器能识别你的卡,从而能够直接办理业务。
服务器端向客户端发送Cookie是通过HTTP响应报文实现的,在Set-Cookie中设置需要像客户端发送的cookie,cookie格式如下:
Set-Cookie: "name=value;domain=.domain.com;path=/;expires=Sat, 11 Jun 2016 11:29:42 GMT;HttpOnly;secure"
其中name=value是必选项,其它都是可选项。Cookie的主要构成如下:
name:一个唯一确定的cookie名称。通常来讲cookie的名称是不区分大小写的;
value:存储在cookie中的字符串值。最好为cookie的name和value进行url编码;
domain:cookie对于哪个域是有效的。所有向该域发送的请求中都会包含这个cookie信息。这个值可以包含子域(如:yq.aliyun.com),也可以不包含它(如:.aliyun.com,则对于aliyun.com的所有子域都有效);
path: 表示这个cookie影响到的路径,浏览器跟会根据这项配置,像指定域中匹配的路径发送cookie;
expires:失效时间,表示cookie何时应该被删除的时间戳(也就是,何时应该停止向服务器发送这个cookie)。如果不设置这个时间戳,浏览器会在页面关闭时即将删除所有cookie;不过也可以自己设置删除时间。这个值是GMT时间格式,如果客户端和服务器端时间不一致,使用expires就会存在偏差;
max-age: 与expires作用相同,用来告诉浏览器此cookie多久过期(单位是秒),而不是一个固定的时间点。正常情况下,max-age的优先级高于expires;
HttpOnly: 告知浏览器不允许通过脚本document.cookie去更改这个值,同样这个值在document.cookie中也不可见。但在http请求张仍然会携带这个cookie。注意这个值虽然在脚本中不可获取,但仍然在浏览器安装目录中以文件形式存在。这项设置通常在服务器端设置;
secure: 安全标志,指定后,只有在使用SSL链接时候才能发送到服务器,如果是http链接则不会传递该信息。就算设置了secure 属性也并不代表他人不能看到你机器本地保存的 cookie 信息,所以不要把重要信息放cookie就对了服务器端设置。
1.2 cookie运行机制
当用户第一次访问并登陆一个网站的时候,cookie的设置以及发送会经历以下4个步骤:
- 客户端发送一个请求到服务器;
- 服务器发送一个HttpResponse响应到客户端,其中包含Set-Cookie的头部;
- 客户端保存cookie,之后向服务器发送请求时,HttpRequest请求中会包含一个Cookie的头部 ;
- 服务器返回响应数据。
浏览器输入地址进行访问,结果如图所示:
可见Response Headers中包含Set-Cookie头部,而Request Headers中包含了Cookie头部。
1.3 cookie属性项
1.3.1 Expires
该属性用来设置Cookie的有效期。Cookie中的maxAge用来表示该属性,单位为秒。Cookie中通过getMaxAge()和setMaxAge(int maxAge)来读写该属性。maxAge有3种值,分别为正数,负数和0。
- 如果maxAge属性为正数,则表示该Cookie会在maxAge秒之后自动失效。浏览器会将maxAge为正数的Cookie持久化,即写到对应的Cookie文件中(每个浏览器存储的位置不一致)。无论客户关闭了浏览器还是电脑,只要还在maxAge秒之前,登录网站时该Cookie仍然有效。下面代码中的Cookie信息将永远有效。
- 当maxAge属性为负数,则表示该Cookie只是一个临时Cookie,不会被持久化,仅在本浏览器窗口或者本窗口打开的子窗口中有效,关闭浏览器后该Cookie立即失效。
- 当maxAge为0时,表示立即删除Cookie
那么maxAge设置为负值和0到底有什么区别呢?
- maxAge设置为0表示立即删除该Cookie,如果在debug的模式下,执行上述方法,可以看见cookie立即被删除了。
- maxAge设置为负数,能看到Expires属性改变了,但Cookie仍然会存在一段时间直到关闭浏览器或者重新打开浏览器。
1.3.2 Cookie的域名
Cookie是不可以跨域名的,隐私安全机制禁止网站非法获取其他网站的Cookie。
正常情况下,同一个一级域名下的两个二级域名也不能交互使用Cookie,比如test1.mcrwayfun.com和test2.mcrwayfun.com,因为二者的域名不完全相同。如果想要mcrwayfun.com名下的二级域名都可以使用该Cookie,需要设置Cookie的domain参数为.mcrwayfun.com,这样使用test1.mcrwayfun.com和test2.mcrwayfun.com就能访问同一个cookie。
一级域名又称为顶级域名,一般由字符串+后缀组成。熟悉的一级域名有baidu.com,qq.com。com,cn,net等均是常见的后缀。
二级域名是在一级域名下衍生的,比如有个一级域名为mcrfun.com,则blog.mcrfun.com和www.mcrfun.com均是其衍生出来的二级域名。
1.3.3 Cookie的路径
path属性决定允许访问Cookie的路径。比如,设置为"/"表示允许所有路径都可以使用Cookie。
1.4 js-cookie插件
$ npm install js-cookie --save
Cookies.set('name', 'value');
Cookies.set('name', 'value', { expires: 7 });
Cookies.set('name', 'value', { expires: 7, path: '' });
Cookies.get('name'); // => 'value'
Cookies.get('nothing'); // => undefined
Cookies.get(); // => { name: 'value' }
Cookies.remove('name');
Cookies.set('name', 'value', { path: '' });
Cookies.remove('name'); // fail!
Cookies.remove('name', { path: '' }); // removed!
// 封装
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
export function getToken() {
return Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
}
export function removeToken() {
return Cookies.remove(TokenKey)
}
2. 短暂的sessionStorage
2.1 是什么
sessionStorage属于webstorage的一种,sessionStorage与我们稍后要说的localStorage类似,可以存储k-v形式的数据,使用方法非常简单set便可以存储。
sessionStorage.setItem('test', 'doctorhou');
sessionStorage.getItem('test');
sessionStorage.removeItem('test');
2.2 访问限制性
- 不同于cookie,sessionStorage的访问限制更高一些,只有当前设定sessionStorage的域下才能访问,而且不同的两个tab之间不能互通。例,我在www.qq.com下种下了sessionStorage,在wx.qq.com下是,无法访问的;
- 在新开的tab下,或者关闭本TAB再打开后(也是www.qq.com),也是无法访问到之前种的sessionStorage的;
- 而本tab刷新的时候,sessionStorage确是可以访问的。
以上种种,证明sessionStorage里面的session,并不同于cookie,是以tab为级别的session。
2.3 做什么用比较好?
既然是存储于客户端而且存储级别仅仅是一个session的话,还是建议存储一些当前页面刷新需要存储,且不需要在tab关闭时候留下的信息。刚刚说了,只有页面刷新才不会清除掉sessionStorage。剩下的均会清理掉sessionStorage,当然,也许可以用sessionStorage来检测用户是否是刷新进入的页面。对于音乐播放器恢复播放进度条等功能等还是挺实用的。
3 简易强大的localStorage
3.1 是什么?
localStorage与sessionStorage较为相似,接口也简单,通过localStorage.setItem/localStorage.getItem即可轻松使用。localStorage的存储周期比sessionStorage长,如果用户不清理的话,是可以永久存储的。
localStorage.setItem('test', 'doctorhou');
localStorage.getItem('test');
var doctorhou = {
name: 'doctorhou',
describe: '高大、威猛、帅气'
};
localStorage.setItem('test', JSON.stringify(doctorhou));
JSON.parse(localStorage.getItem('test'));
3.2 访问的限制性
localStorage与sessionStorage虽然相似,但是访问限制却不尽相同,localStorage的访问域默认设定为设置localStorage的当前域,其他域名不可以取。这点与sessionStorage相同,但是与sessionStorage不同的是,localStorage设定后,新开tab是可以访问到的。
3.3 存储时间
localStorage理论上讲是永久性质的存储。但是,免不了用户会使用浏览器清除数据,或者浏览器有时候为了节省,去清除数据。
3.4 大小限制及检测
localStorage的大小一般限定为4M左右,这点可以根据实际浏览器来测试。那么,如何检测自己已经消耗了多少空间呢?可以直接将localStorage序列化,看看其字节数,就可以算出其已经占用的空间了(可能会有点误差,这样做会把一些转移符号算进去,可以消除掉后再算)。
3.5 做什么用比较好
由于localStorage的稳定性质,及其长效的存储。笔者建议如果有一些数据,服务器难以承载其压力,但又要与用户的信息绑定的话,可以使用localStorage存储一些状态,这样即能缓解服务端压力,也可以存储用户的数据。当然,也有一些小技巧,可以用localStorage提高网站访问的速度。如笔者写的一篇浅析文章:
聊一聊百度移动端首页前端速度那些事儿
4 websql与indexeddb
4.1 是什么?
这两位可是web存储中的重型武器。为什么放在一起说呢,是因为,websql的标准,官方已经不打算维护了,转而维护了新的indexeddb,读者可能会问了,那直接说indexeddb就好了,为啥还要说websql,因为websql虽然过时了,但是其兼容性却出奇的好,几乎是移动端均可用呀。我们来看一下caniuse上的兼容性数据:
websql的兼容性如图
indexeddb的兼容性却不是很好,android4.4之前以及ios7以前都无法使用。
所以笔者建议如下,能用indexeddb的时候,就要使用indexeddb,因为其代表了未来的发展方向。如果用不了indexeddb的尽量使用websql进行代替。其实就是使用一段腻子脚本(polyfill)即可,做一下兼容。接下来我们会尝试做一点腻子脚本。
websql更像是关系型数据库,并且使用sql语句进行操作
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
var db = window.openDatabase('testDB', '1.0', 'TestDb', 2 * 1024 * 1024);
db.transaction(function (context) {
context.executeSql('CREATE TABLE IF NOT EXISTS cubefe(id, name)');
context.executeSql('INSERT INTO cubefe (id, name) VALUES (1, "doctorhou")');
});
</script>
</body>
</html>
indexeddb更像是nosql,直接使用js的方法操作数据即可,
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
var startTime = +new Date();
console.log('starttime:', startTime);
function openDB(dbname, version, cb) {
var request = window.indexedDB.open(dbname);
request.onsuccess = function (e) {
var db = e.target.result;
myDB.db = db;
var version = db.version;
if (!db.objectStoreNames.contains('students')) {
request = db.createObjectStore('students', {autoIncrement: true});
}
cb && cb(e);
};
}
var myDB = {
name: 'test',
version: 4,
db: null
};
openDB(myDB.name, myDB.version, function (e) {
var db = e.target.result;
var storeName = 'students';
var transaction = db.transaction(storeName, 'readwrite');
var store = transaction.objectStore(storeName);
store.put({id: 2, name: 'doctorhou'}, 'b');
var request = store.get(1);
request.onsuccess = function (e) {
console.log(request.result);
var endTime = +new Date();
console.log('take-time:', endTime - startTime);
};
});
</script>
</body>
</html>
4.2 访问
indexeddb和websql在这一点上与localStorage一致,均是在创建数据库的域名下才能访问。而且不能指定访问域名。
4.3 存储时间
这两位的存储时间也是永久,除非用户清除数据,可以用作长效的存储。
4.4 大小限制
理论上讲,这两种存储的方式是没有大小限制的。然而indexeddb的数据库超过50M的时候浏览器会弹出确认。基本上也相当于没有限制了。
4.5 性能如何?
我这边做了个实验,indexeddb查询少量数据也就花费了20MS左右。还是很快的。
大量数据的情况下,相对耗时会变长一些,但是也就在30MS左右,也是相当给力了。如图4.5.2所示,10W数据+,毕竟nosql。
而websql这边的效率也不错,10w+数据,简单查询一下,只花费了20MS左右。
4.6 拿来干什么用?
当我们是在做一个离线应用,或者webapp的时候,可以考虑使用本地数据库中存取数据。如果不存大量的数据的话,其实localStorage就够用了。亦或者,你想把一张用户的皮肤图片之类的大量数据存入客户端缓存起来,localStorage已经不够用了的话,也可以尝试一下websql与indexeddb。