# Cookie
cookie 应用场景: 登录网站,第一天输入用户名密码登录了,第二天再打开很多情况下就直接打开了。这个时候用到的一个机制就是 cookie。
cookie 的跨域共享不是无条件的,即:请求和响应的 IP 完全不相同时,无法实现 cookie 共享,这就相当于 A 网站不能访问 B 网站的 cookie 一个道理。当请求发起方和接收方的域名(IP)完全一致,端口号不同时,浏览器是可以携带 cookie 的,也就是:服务器能接收到前端所传来的 cookie。
服务器通过设置set-cookie
这个响应头,将 cookie 信息返回给浏览器,浏览器将响应头中的 cookie 信息保存在本地,当下次向服务器发送 HTTP 请求时,浏览器会自动将保存的这些 cookie 信息添加到请求头中。
通过 cookie,服务器就会识别出浏览器,从而保证返回的数据是这个用户的。
- 通过
set-cookie
设置 - 下次请求会自动带上
- 键值对,可设置多个
生命周期为只在设置的 cookie 过期之前一直有效,即使窗口或浏览器关闭。存储大小为 4k 左右,再长了会被截断。有个数限制(各个浏览器不同),一般不能超过 20 个。与服务器端通信:每次都会携带在 http 头中,如果使用 cookie 保存过多数据就会带来性能问题。
cookie 无法设置除当前域名或者其父域名之外的其他 domain。这个是浏览器出于对 cookie 的保护造成的,也就是 cookie 无法跨域设置。对于子域名也有如下规则,当前域名只能设置当前域名以及他的父域名,不能设置子域名。
# 属性
- max-age
- 过期时间有多长
- 默认在浏览器关闭时失效
- expires
- 到哪个时间点过期
- secure
- 表示这个 cookie 只会在 https 的时候才会发送
- HttpOnly
- 设置后无法通过在 js 中使用 document.cookie 访问
- 保障安全,防止攻击者盗用用户 cookie
- domain
- 表示该 cookie 对于哪个域是有效的。
# 优点
极高的扩展性和可用性
- 通过良好的编程,控制保存在 cookie 中的 session 对象的大小。
- 通过加密和安全传输技术(SSL),减少 cookie 被破解的可能性。
- 只在 cookie 中存放不敏感数据,即使被盗也不会有重大损失。
- 控制 cookie 的生命期,使之不会永远有效。偷盗者很可能拿到一个过期的 cookie。
# 缺点
cookie
虽然在持久保存客户端数据提供了方便,分担了服务器存储的负担,但还是有很多局限性的。
Cookie
数量的限制。每个特定的域名下最多生成的个数有限制。 IE6 或更低版本最多 20 个 cookie, IE7 和之后的版本最后可以有 50 个 cookie, Firefox 最多 50 个 cookie, chrome 和 Safari 没有做硬性限制。IE
和Opera
会清理近期最少使用的cookie
,Firefox
会随机清理cookie
。Cookie
长度的限制。每个 cookie 长度不能超过 4KB,否则会被截掉。- 安全性问题。如果 cookie 被人拦截了,那人就可以取得所有的 session 信息。即使加密也与事无补,因为拦截者并不需要知道 cookie 的意义,他只要原样转发 cookie 就可以达到目的了。
- 有些状态不可能保存在客户端。例如,为了防止重复提交表单,我们需要在服务器端保存一个计数器。如果我们把这个计数器保存在客户端,那么它起不到任何作用。
- IE6 或更低版本最多 20 个 cookie
- IE7 和之后的版本最后可以有 50 个 cookie。
- Firefox 最多 50 个 cookie
- chrome 和 Safari 没有做硬性限制
IE
和Opera
会清理近期最少使用的cookie
,Firefox
会随机清理cookie
。cookie
的最大大约为4096
字节,为了兼容性,一般不能超过4095
字节。
# js 操作 cookie
domain 的设置,有两点要注意:
- 在 setCookie 中省略 domain 参数,那么 domain 默认为当前域名。
- domain 参数可以设置父域名以及自身,但不能设置其它域名,包括子域名,否则 cookie 不起作用。
那么 cookie 的作用域:cookie 的作用域是 domain 本身以及 domain 下的所有子域名。
// 创建cookie
function setCookie(name, value, expires, path, domain, secure) {
var cookieText = encodeURIComponent(name) + '=' + encodeURIComponent(value);
if (expires instanceof Date) {
cookieText += '; expires=' + expires;
}
if (path) {
cookieText += '; path=' + path;
}
if (domain) {
cookieText += '; domain=' + domain;
}
if (secure) {
cookieText += '; secure';
}
document.cookie = cookieText;
}
// 获取cookie
function getCookie(name) {
var cookieName = encodeURIComponent(name) + '=';
var cookieStart = document.cookie.indexOf(cookieName);
var cookieValue = null;
if (cookieStart > -1) {
var cookieEnd = document.cookie.indexOf(';', cookieStart);
if (cookieEnd == -1) {
cookieEnd = document.cookie.length;
}
cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
}
return cookieValue;
}
// 删除 cookie 方法 直接设置 0 的时间
function unsetCookie(name) {
document.cookie = name + '= ; expires=' + new Date(0);
}
# Cookie 隔离(请求资源的时候不要让它带 Cookie 怎么做)
- 如果静态文件都放在主域名下,那静态文件请求的时候都带有的 cookie 的数据提交给 server 的,非常浪费流量,所以不如隔离开。
- 因为 cookie 有域的限制,因此不能跨域提交请求,故使用非主要域名的时候,请求头中就不会带有 cookie 数据,这样可以降低请求头的大小,降低请求时间,从而达到降低整体请求延时的目的。
- 同时这种方式不会将 cookie 传入 Web Server,也减少了 Web Server 对 cookie 的处理分析环节,提高了 webserver 的 http 请求的解析速度。
# Session
session 应用场景: 一个场景是购物车,添加了商品之后客户端处可以知道添加了哪些商品,而服务器端如何判别呢,所以也需要存储一些信息就用到了 session。
- 存放在服务器的一种用来存放用户数据的类似 HashTable 的结构
- 浏览器第一次发送请求时,服务器自动生成了 HashTable 和 SessionID 来唯一标识这个 hash 表,并将 sessionID 存放在 cookie 中通过响应发送到浏览器。浏览器第二次发送请求会将前一次服务器响应中的 sessionID 随着 cookie 发送到服务器上,服务器从请求中提取 sessionID,并和保存的所有 sessionID 进行对比,找到这个用户对应的 hash 表。
- 一般这个值是有时间限制的,超时后销毁,默认 30min
- 当用户在应用程序的 web 页面间挑转时,存储在 session 对象中的变量不会丢失而是在整个用户会话中一直存在下去。
- session 依赖于 cookie,因为 sessionID 是存放在 cookie 中的。
# Web Storage
在较高版本的浏览器中,js
提供了sessionStorage
和globalStorage
。在HTML5
中提供了localStorage
来取代globalStorage
。
# localStorage
localStorage 对象存储的数据没有时间限制。localStorage 生命周期是永久的,这意味着除非用户显示在浏览器提供的 UI 上清除 localStorage 的信息,苟泽这些信息将永远存在。存放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。
# localStorage 的过期
//封装过期控制代码
function set(key, value) {
var curTime = new Date().getTime();
localStorage.setItem(key, JSON.stringify({ data: value, time: curTime }));
}
function get(key, exp) {
var data = localStorage.getItem(key);
var dataObj = JSON.parse(data);
if (new Date().getTime() - dataObj.time > exp) {
console.log('信息已过期');
} else {
var dataObjDataToJson = JSON.parse(dataObj.data);
return dataObjDataToJson;
}
}
使用场景: 1.利用本地数据,减少网络传输 2.弱网络环境下,高延迟,低带宽,尽量把数据本地化
使用方法:
<script>
window.onload = function() {
var Ipt = document.getElementById('input1');
var value = '{"name":"和派孔明","Age":"18","address":"陆家嘴金融城"}';
set('information', value);
Ipt.onclick = function() {
//var dataObjData=get('information',1000);//过期时间为1秒,正常情况下,你点击的时候已经过期
//var dataObjData=get('information',1000*60);//过期时间为1分钟
//var dataObjData=get('information',1000*60*60);//过期时间为1小时
//var Obj=get('information',1000*60*60*24);//过期时间为24小时
var dataObjData = get('information', 1000 * 60 * 60 * 24 * 7); //过期时间为1周
console.log(dataObjData || null);
if (dataObjData != '' && dataObjData != null) {
console.log('姓名:' + dataObjData.name);
console.log('年龄:' + dataObjData.Age);
console.log('地址:' + dataObjData.Age);
} else {
alert('获取的信息已经过期');
}
};
};
</script>
# LocalStorage 存储 JSON 对象的问题
- localStorage - 没有时间限制的数据存储
var arr = [1, 2, 3];
localStorage.setItem('temp', arr); //会返回1,2,3
console.log(typeof localStorage.getItem('temp')); //string
console.log(localStorage.getItem('temp')); //1,2,3
- localStorage.setItem() 不会自动将 Json 对象转成字符串形式
var obj = { a: 1, b: 2 };
typeof localStorage.getItem('temp2'); //也会返回String
localStorage.setItem('temp2', obj); //但是返回[object Object]
- 用 localStorage.setItem()正确存储 JSON 对象方法是:
- 存储前先用 JSON.stringify()方法将 json 对象转换成字符串形式
JSON.stringify()
方法可以将任意的 JavaScript 值序列化成 JSON 字符串
var obj = { a: 1, b: 2 };
obj = JSON.stringify(obj); //转化为JSON字符串
localStorage.setItem('temp2', obj); //返回{"a":1,"b":2}
- 后续要操作该 JSON 对象,要将之前存储的 JSON 字符串先转成 JSON 对象再进行操作
obj = JSON.parse(localStorage.getItem('temp2'));
# sessionStorage
sessionStorage
用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此sessionStorage
不是一种持久化的本地存储,仅仅是会话级别的存储。当用户关闭浏览器窗口后,数据就会被删除。
存放数据大小为一般为 5MB,而且它仅在客户端(即浏览器)中保存,不参与和服务器的通信。
# globalStorage
目前只有 FF 支持,且只支持当前域下的 globalStorage 存储。
基本用法:
- globalStorage['developer.mozilla.org'] —— 在 developer.mozilla.org 下面所有的子域都可以通过这个存储对象来进行读和写。
- globalStorage['mozilla.org'] —— 在 mozilla.org 域名下面的所有网页都可以通过这个存储对象来进行读和写。
- globalStorage['org'] —— 在.org 域名下面的所有网页都可以通过这个存储对象来进行读和写。
- globalStorage[''] —— 在任何域名下的任何网页都可以通过这个存储对象来进行读和写。
方法属性:
- setItem(key, value) —— 设置或重置 key 值。
- getItem(key) —— 获取 key 值。
- removeItem(key) —— 删除 key 值。
设置 key 值:window.globalStorage["qq.com"].key = value; 获取 key 值:value = window.globalStorage["qq.com"].key;
# 对比
# Cookie 和 Session 的区别
- cookie 数据存放在客户的浏览器上,session 数据放在服务器上。
- cookie 不是很安全,可以分析存放在本地的 cookie 并进行 cookie 欺骗,考虑到安全应当使用 session, 容易伪造,不如 session 安全
- session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用 cookie
- session 会消耗大量服务器资源,cookie 在每次 HTTP 请求中都会带上,影响网络性能。
- 域的支持范围不一样,比方说 a.com 的 Cookie 在 a.com 下都能用,而 www.a.com 的 session 在 api.a.com 下都不能用
- 单个 cookie 保存的数据不能超过 4k,很多浏览器都限制一个站点最多保存 20 个 cookie。但是 session 没有限制 ?TODO:
- 所以个人建议:将登陆信息等重要信息存放为 session;其他信息如果需要保留,可以放在 cookie 中
cookie 和 session 都可以用来存储用户信息,cookie 存放于客户端,session 存放于服务端,因为 cookie 存放于客户端 有可能被窃取,因此 cookie 一般用来存放不敏感的信息,如用户设置的网站主题等,敏感的信息采用 session 存储,如用户 的登陆信息,session 可以存放于文件、数据库、内存中都可以,cookie 可以服务端响应的时候设置,也可以客户端通过 js 设置 cookie 会在请求时在 http 首部发送给客户端,cookie 一般在客户端有大小限制,一般为 4k。
# web storage 和 Cookie 的区别
web storage
的概念和cookie
相似,区别是它是为了更大容量存储设计的。
cookie
的大小是受限的- 并且每次你请求一个新的页面的时候
cookie
都会被发送过去,这样无形中浪费了带宽 - 另外
cookie
还需要指定作用域,不可以跨域调用。 - 除此之外,
web storage
拥有setItem,getItem,removeItem,clear
等方法,localStorage
和sessionStorage
都具有相同的操作方法,例如setItem、getItem
和removeItem
等,不像cookie
需要前端开发者自己封装setCookie,getCookie
。 - 但是
cookie
也是不可以或缺的:cookie
的作用是与服务器进行交互,作为HTTP
规范的一部分而存在 ,而web storage
仅仅是为了在本地“存储”数据而生。 - 浏览器的支持除了
IE7
及以下不支持外,其他标准浏览器都完全支持(ie 及 FF 需在 web 服务器里运行),值得一提的是 IE 总是办好事,例如 IE7、IE6 中的userData
其实就是javascript
本地存储的解决方案。通过简单的代码封装可以统一到所有的浏览器都支持web storage
。
# Cookie, sessionStorage 和 localStorage 的区别。
sessionStorage、localStorage、cookie 都是在浏览器端存储的数据,其中 sessionStorage 的概念很特别,引入了一个 "浏览器窗口" 的概念。sessionStorage 是在同源的同窗口(或 tab)中,始终存在的数据。也就是说只要这个浏览器窗口没有关闭,即使刷新页面或进入同源另一页面,数据仍然存在。关闭窗口后,sessionStorage 即被销毁。同时“独立”打开的不同窗口,即使是同一页面,sessionStorage 对象也是不同的
- cookie 是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)
- cookie 数据始终在同源的 http 请求中携带(即使不需要),记会在浏览器和服务器间来回传递。
- sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存。
- 存储大小: cookie 数据大小不能超过 4k。 sessionStorage 和 localStorage 虽然也有存储大小的限制,但比 cookie 大得多,可以达到 5M 或更大。
- 有效期(生命周期): localStorage: 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据; sessionStorage: 数据在当前浏览器窗口关闭后自动删除。 cookie: 设置的 cookie 过期时间之前一直有效,即使窗口或浏览器关闭
- 共享 sessionStorage 不能共享,localStorage 在同源文档之间共享,cookie 在同源且符合 path 规则的文档之间共享
cookies 会发送到服务器端。其余两个不会。
- Cookie
- 每个域名存储量比较小(各浏览器不同,大致 4K)
- 所有域名的存储量有限制(各浏览器不同,大致 4K)
- 有个数限制(各浏览器不同)
- 会随请求发送到服务器
- LocalStorage
- 永久存储
- 单个域名存储量比较大(推荐 5MB,各浏览器不同)
- 总体数量无限制
- SessionStorage
- 只在 Session 内有效
- 存储量更大(推荐没有限制,但是实际上各浏览器也不同)
# Cookie,localStorage,sessionStorage,indexDB
我们先来通过表格学习下这几种存储方式的区别
特征 | cookie | localStorage | sessionStorage | indexDB |
---|---|---|---|---|
数据生命周期 | 一般由服务器生成,可以设置过期时间 | 除非被清理,否则一直存在 | 页面关闭就清理 | 除非被清理,否则一直存在 |
数据存储大小 | 4K | 5M | 5M | 无限 |
与服务端通信 | 每次都会携带在 header 中,对于请求性能影响 | 不参与 | 不参与 | 不参与 |
从上表可以看到,cookie
已经不建议用于存储。如果没有大量数据存储需求的话,可以使用 localStorage
和 sessionStorage
。对于不怎么改变的数据尽量使用 localStorage
存储,否则可以用 sessionStorage
存储。
# localStorage、sessionStorage 的区别?还要去了解一下 Cookie
在 HTML5 之前,主要是使用 cookies 存储,cookies 的缺点有:需要再请求头上带着数据,存储大小不过在 4k 之内。 HTML5 web 存储,是一个比 cookie 更好的本地存储方式。使用 HTML5 可以在本地存储用户的浏览数据。
客户端存储数据的两个对象为:
他们拥有相同的 API:
- 保存数据:
localStorage.setItem(key,value)
- 读取数据:
localStorage.getItem(key)
- 删除单个数据:
localStorage.removeItem(key)
- 得到某个索引的 key:
localStorage.key(index)
localStorage、sessionStorage、Cookie 共同点:都是保存在浏览器端,且同源的。 TODO: ?
# localStorage 和 sessionStorage 区别
- 不同浏览器无法共享 localStorage 和 sessionStorage 中的信息。
- 相同浏览器的不同页面间可以共享相同的 localStorage(页面属于相同域名和端口);但是不同页面或标签页(仅指顶级窗口)间无法共享 sessionStorage 的信息。
- localStorage - 没有时间限制的数据存储;sessionStorage - 针对一个 session 的数据存储
# 其他
# WEB 应用从服务器主动推送 Data 到客户端有那些方式?
- html5 websocket
- WebSocket 通过 Flash
- XHR 长时间连接
- XHR Multipart Streaming
- 不可见的 Iframe
- script 标签的长时间连接(可跨域)
# 如何实现浏览器内多个标签页之间的通信?
同源情况下,调用 localStorage, cookies 等本地存储方式都可以进行通信。
# uerData
IE 提供了一种存储可以持久化用户数据,叫做uerData
,从IE5.0
就开始支持。每个数据最多 128K,每个域名下最多 1M。这个持久化数据放在缓存中,如果缓存没有清理,那么会一直存在。
# cookie 和 token 都存放在 header 中,为什么不会劫持 token?
- 攻击者通过 xss 拿到用户的 cookie 然后就可以伪造 cookie 了。
- 或者通过 csrf 在同个浏览器下面通过浏览器会自动带上 cookie 的特性 在通过 用户网站-攻击者网站-攻击者请求用户网站的方式 浏览器会自动带上 cookie 但是 token
- 不会被浏览器带上 问题 2 解决
- token 是放在 jwt 里面下发给客户端的 而且不一定存储在哪里 不能通过 document.cookie 直接拿到,通过 jwt+ip 的方式 可以防止 被劫持 即使被劫持 也是无效的 jwt
# 什么是 Cookie 隔离?(请求资源的时候不要让它带 cookie 怎么做)
静态资源放 CDN ,用 cookie free domain
如果静态文件都放在主域名下,那静态文件请求的时候都带有的 cookie 的数据提交给 server 的,非常浪费流量, 所以不如隔离开。
因为 cookie 有域的限制,因此不能跨域提交请求,故使用非主要域名的时候,请求头中就不会带有 cookie 数据, 这样可以降低请求头的大小,降低请求时间,从而达到降低整体请求延时的目的。
同时这种方式不会将 cookie 传入 Web Server,也减少了 Web Server 对 cookie 的处理分析环节, 提高了 webserver 的 http 请求的解析速度。
# 模拟实现一个 localStorage
'use strict';
const valuesMap = new Map();
class LocalStorage {
getItem(key) {
const stringKey = String(key);
if (valuesMap.has(key)) {
return String(valuesMap.get(stringKey));
}
return null;
}
setItem(key, val) {
valuesMap.set(String(key), String(val));
}
removeItem(key) {
valuesMap.delete(key);
}
clear() {
valuesMap.clear();
}
key(i) {
if (arguments.length === 0) {
throw new TypeError("Failed to execute 'key' on 'Storage': 1 argument required, but only 0 present."); // this is a TypeError implemented on Chrome, Firefox throws Not enough arguments to Storage.key.
}
var arr = Array.from(valuesMap.keys());
return arr[i];
}
get length() {
return valuesMap.size;
}
}
const instance = new LocalStorage();
global.localStorage = new Proxy(instance, {
set: function(obj, prop, value) {
if (LocalStorage.prototype.hasOwnProperty(prop)) {
instance[prop] = value;
} else {
instance.setItem(prop, value);
}
return true;
},
get: function(target, name) {
if (LocalStorage.prototype.hasOwnProperty(name)) {
return instance[name];
}
if (valuesMap.has(name)) {
return instance.getItem(name);
}
},
});
# 实现 Storage,使得该对象为单例,并对 localStorage 进行封装设置值 setItem(key,value)和 getItem(key)
var instance = null;
class Storage {
static getInstance() {
if (!instance) {
instance = new Storage();
}
return this.instance;
}
setItem = (key, value) => localStorage.setItem(key, value),
getItem = key => localStorage.getItem(key)
}