客户端存储

78 阅读6分钟

前言

随着web应用的发展,对于客户端存储信息的需求也在增强,无论是用户信息、偏好等信息,都有必要存储在客户端

目前常用的客户端存储方案有三种:

  • cookie
  • 浏览器存储API
  • IndexDB

cookie

cookie全称HTTP cookie,最初用于客户端存储会话信息。这个规范要求服务器在响应HTTP请求时,通过发送Set-Cookie HTTP头部包含会话信息。

如下例:

image.png

上面的服务端响应会设置一个名称为ttype的cookie,其值为WEB,并设置了有效期等信息

浏览器会存储这个cookie,并在之后的所有请求中都会通过HTTP头部将cookie回发给服务器

限制

  1. cookie是与特定域绑定的。在发送请求时,它会与请求一起发送到创建它的域。这个限制能保证cookie中存储的信息只对被认可的接收者开放,不被其他域接收
  2. cookie的大小和数量也会被限制:
  • 浏览器不超过300个cookie
  • 每个cookie不超过4096字节,也即4KB
  • 每个域不超过20个cookie
  • ... 如果cookie超出单个域的限制,那么浏览器就会删除之前设置的cookie 如果cookie超出大小限制,那么该cookie会被静默删除

cookie的构成

cookie在浏览器中有如下参数:

  • 名称:也即键。cookie名不区分大小写,因此myCookie和MyCookie是一样的,但是仍然建议进行区分,因为某些服务器软件会区别对待。cookie名必须经过URL编码
  • 值:存储在cookie里面的字符串值,这个值必须经过URL编码
  • 域:cookie有效的域。发送到这个域的所有请求都会包含对应的cookie。这个值可能包含子域,例如www.voiceclub.cn ,也可以不包含,例如:.voiceclub.cn,表示包含voiceclub.cn下面的所有子域
  • 路径:表示请求URL中包含该路径才会把cookie发送到服务器,例如可以指定cookie只能由www.voiceclub.cn/allaudio/ 访问,因此www.voiceclub.cn/ 下的页面就不会发送cookie,即使请求的是同一个域。默认情况下,该值为/表示全部
  • 过期时间:表示何时删除cookie的时间戳。默认情况下,浏览器会在会话结束后删除所有的cookie,不过也可以设置cookie的过期时间,这样即便关闭浏览器cookie也会保存在浏览器上。过期时间设置为过去的时间会立即删除该cookie
  • 安全标志:设置之后只有使用SSL安全连接的情况下才会把cookie发送到服务器。例如请求www.voiceclub.cn/ 会发送cookie,而www.voiceclub.cn/ 则不会

image.png

上述的这些参数都在Set-Cookie头部中使用分号加空格隔开,例如上面的示例

当在实际向服务器发送cookie时,只有名称和值会发送到服务器

最佳实践

如果不通过服务端“种”cookie的方式,前端在js中也可以使用相关API实现cookie的设置

document.cookie = 'name:smx';

代码非常简单,如果需要像服务端那样增加其他的参数的话,只需要在后面继续拼写其他参数就可以了:

document.cookie = 'name:smx; domain:.voiceclub.cn';

上面的名称和值字符并不需要编码,但是仍然推荐使用URL编码的方式书写

document.cookie = encodeURIComponent('name') + '=' + encodeURIComponent('smx') + '; domain=.voiceclub.cn';

为了方便cookie的设置与获取,最佳实践是声明一个cookie工具类来实现相关功能:

        class CookieUtil {
            static set(name, value, expires, path, domain, secure) {
                let cookieText = `${encodeURIComponent(name) = encodeURIComponent(value)}`;

                if (expires instanceof Date) {
                    cookieText += `; expires=${expires.toGMTString()}`;
                }

                if (path) {
                    cookieText += `; path=${path}`;
                }

                if (domain) {
                    cookieText += `; domain=${domain}`;
                }

                if (secure) {
                    cookieText += `; secure=`;
                }

                document.cookie = cookieText;
            }

            static get(name) {
                const cookieName = encodeURIComponent(name) + '=';
                const cookieStart = document.cookie.indexOf(cookieName);
                let cookieValue = null;
                if (cookieStart > -1) {
                    let cookieEnd = document.cookie.indexOf(';', cookieStart);
                    // 最后一个cookie
                    if (cookieEnd === -1) {
                        cookieEnd = document.cookie.length;
                    }
                    cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));

                    return cookieValue;
                }
            }

            static unset(name, path, domain, secure) {
                CookieUtil.set(name, '', new Date(0), path, domain, secure);
            }
        }

Web Storage

Storage类型用于保存键值对,它的实例与其他对象一样,但增加了以下方法:

  • clear 清空全部的键值对 未在Firefox中实现
  • getItem(name) 获取对应键名的值
  • key(index) 获取对应索引位置的值
  • removeItem(name) 移除对应键名的键值对
  • setItem(name, value) 设置给定键名的值

下面以sessionStorage为例,演示上面的方法以及属性写法

window.sessionStorage.setItem('1', 1);
console.log(window.sessionStorage);
/* 
{
    "IsThisFirstTime_Log_From_LiveServer": "true",
    "1": "1"
}
*/
console.log(window.sessionStorage.key(1)); // 1
window.sessionStorage.clear();
console.log(window.sessionStorage.getItem('1')) // null
window.sessionStorage.name = 'smx';
console.log(window.sessionStorage);
/* 
    {
        "IsThisFirstTime_Log_From_LiveServer": "true",
        "name": "smx"
    }
*/
delete window.sessionStorage.name;
console.log(window.sessionStorage);
/*
    {
        "IsThisFirstTime_Log_From_LiveServer": "true"
    }
*/

虽然通过属性的方式也可以实现类似的功能,但是仍然建议使用给出的方法

sessionStorage

sessoinStorage只作用于当前会话,可以理解为当前tab页,当当前tab页关闭后,该数据会被清空

即使开启两个相同的网站,在其中一个网站设置sessionStorage,在另外一个页面也不会生效这意味着sessionStorage只对当前tab页面生效

通过刷新页面的方式并不会导致sessionStorage丢失

localStorage

localStorage提供了本地持久化存储能力,它保存的数据会直到js调用API清除或者用户手动清除浏览器缓存才删除

存储事件

每当storage对象发生变化时,都会在文档上触发storage事件。使用属性或setItem()设置值、使用delete或removeItem()删除值,以及每次调用clear()时都会触发这个事件

该事件有如下四个属性:

  • domain:存储变化对应的域
  • key:被设置或删除的键
  • newValue:键被设置的新值,若键被删除则为null
  • oldValue:键变化之前的值

例如:

window.addEventListener('storage', (event) => {
    console.log(event);
});
window.localStorage.setItem('name', 'smx');
window.localStorage.setItem('age', 24);
window.localStorage.clear();

要注意的是,storage事件只会在同源且打开的其他页面被触发,例如某个页面修改storage对象另外一个页面进行监听,是可以监听到对象变化的,而在自身页面修改属性并监听是无法获取到变化的,这一点很重要,意味着storage事件就是实现同源网页之间的storage通信的

限制

与cookie相同,storage也有限制,一般来说每个源(协议、域、端口)会限制5M的大小,原因个人推测为防止用户数据被滥用以及保护系统存储空间

IndexDB

IndexDB已经发行很多年了,但是对于它几乎没有接触,毕竟在前端方面需要使用数据库存储数据的案例实在是太少。而且各种浏览器对于IndexDB的兼容方案也不统一,最大的问题是IndexDB的学习成本很高,要了解相当的的概念以及API,因此暂不展开对于它的了解