浏览器存储型缓存Cookie、Web Storage、IndexDB

474 阅读5分钟

参考书籍

前端缓存技术与方案解析

Cookie


Cookie 作为最老牌的存储型缓存,其诞生之初其实并不是为了提供浏览器存储的功能,而是为了辨别用户身份。实现页面的登录从而避免页面重复登录就是个很好的例子;

为什么Cookie只适合存储身份信息,而其它信息不建议放在Cookie内?
  • Cookie的存储空间特别小,所有的Cookie总和不能超过4KB
  • Cookie在同域下会被自动携带进行接口请求**,试想如果Cookie存储了大量非身份信息,在每次接口请求时都会携带上,势必会产生一些资源浪费;
如何操作Cookie
// 存储 Cookie 
document.cookie='name=juejin; domain=juejin.cn' 
// 读取 Cookie 只能通过 document.cookie 读取所有 Cookie 并进行字符串截取,非常不便 

// 删除 Cookie 
let date = new Date() 
date.setTime(date.getTime() + 10000) 
// 设置一个过期时间 
document.cookie=`name=test; domain=juejin.cn; expires=${date.toGMTString()}`

如果我们需要增删改查cookie的话,绕不过对字符串的处理,虽然逻辑并不复杂,但是为了确保高效率以及万无一失,推荐使用js-cookie库对cookie进行操作。

import Cookies from 'js-cookie'
// 存储
Cookie Cookies.set('name', 'juejin', { domain: 'juejin.cn' }) 
// 读取 
Cookie Cookies.get('name') 
// 删除
Cookie Cookies.remove('name')
客户端存储token信息的方式有哪几种?

1、服务器自动植入
当我们实现登录的时候,将账号和密码通过接口的形式发送给服务端,服务端校验完账号密码,如果正确,会在服务端生成token,然后将token写入浏览器cookie中,通过返回前端的响应报头中设置首部字段 set-cookie 来将 token 信息植入浏览器 cookie 中。后续同域下的所有请求都会自动携带该cookie进行服务端通信。

2、前端手动植入
浏览器端支持cookie可以通过自动植入的方式进行cookie存储,但是在小程序或者APP没有浏览器环境,故不支持cookie。 针对于这些场景需要前端手动的植入token信息,可以和服务端沟通,将token放到接口响应实体中,前端拿到token,将token缓存在本地Web Storage中,然后对http请求进行封装,每个请求都携带上token

Web Storage


在开发中,有很多场景需要在前端缓存一些数据,如果和身份不相关的话,放在cookie不合适,所以HTML5推出了WebStorage浏览器存储机制,其又分为sessionStorage和localStorage。 其可存储的大小在不同浏览器有不同的空间,整体2.5M~10M的空间;

  • Session Storage 作为临时性的本地存储,其生命周期存在于网页会话期间,即使用 Session Storage 存储的缓存数据在网页关闭后会自动释放,并不是持久性的。
  • Local Storage 则存储于浏览器本地,除非手动删除,否则其一直存在,属于持久性缓存,其本身不支持设置过期时间,但是可以通过二次封装来实现过期时间机制。

另外其两个共同的一个特点:存储的数据都会被转成字符串类型,所以在存储对象时,我们要进行序列化处理JSON.stringfy(obj),还有存储Number类型时会被转成字符串等行为

// 存储 Local Storage 数据
localStorage.setItem('name', 'juejin')
// 读取 Local Storage 数据 
localStorage.getItem('name')
// 删除 Local Storage 数据 
localStorage.removeItem('name')
如何对localStorage封装,让其具备过期时间、存储对象的能力?
let storage = {
    // 存储方法
    setStorage: function (key, value, duration) {
        let data = {
            value: value,
            expiryTime: !duration || isNaN(duration) ? 0 : this.getCurrentTimeStamp() + parseInt(duration)
        }
        
        localStorage[key] = JSON.stringify(data) // 进行序列化操作
    },
    // 获取方法
    getStorage: function (key) {
        let data = localStorage[key]
        
        if (!data || data === "null") {
            return null
        }
       
        let now = this.getCurrentTimeStamp()
        let obj
    
        try {
            obj = JSON.parse(data); // 进行反序列化操作
        } catch (e) {
            return null
        }
        
        if (obj.expiryTime === 0 || obj.expiryTime > now) {
            return obj.value
        }
        
        return null
    },
    // 删除方法
    removeStorage: function (key) {
        localStorage.removeItem(key)
    },
    // 获取当前时间戳
    getCurrentTimeStamp: function () {
        return Date.parse(new Date())
    }
}

IndexDB


IndexDB.png

在平时开发中,cookie和Web Storage几乎能满足我们的业务开发场景,但是针对于特殊场景,需要在客户端存储较大数据时,可以考虑采用IndexDB(如果兼容性可以介绍)。 WebStorage最大可存储10M,而IndexDB支持不低于250M的空间。

IndexDB用法

获取到新建或者打开的数据库

// 首先判断浏览器是否支持
if (!('indexedDB' in window)) {
    console.log('浏览器不支持 indexedDB')
    return
}

let idb;

// 打开名为 juejin,版本号为 1 的数据库,如果不存在则自动创建
let request = window.indexedDB.open('juejin', 1)

// 错误回调
request.onerror = function (event) {
    console.log('打开数据库失败')
}

// 成功回调
request.onsuccess = function (event) {
    idb = request.result
    console.log('打开数据库成功')
}

onupgradeneeded:版本变化触发。 例如新建数据库或更新数据库版本时会触发

request.onupgradeneeded = function(e) {
    idb = e.target.result;
    console.log('running onupgradeneeded')
    
    // 新建对象表时,先判断该表是否存在
    if (!idb.objectStoreNames.contains('store')) {
        // 创建名为 store 的表,以 id 为主键
        let storeOS = idb.createObjectStore('store', { keyPath: 'id' })
    }
};

往库里插入数据

// 新增方法
function addItem(item) {
    // 新增时必须指定表名和操作模式
    let transaction = idb.transaction(['store'], 'readwrite')
    // 获取表对象
    let store = transaction.objectStore('store')
    // 调用 add 方法新增数据
    store.add(item)
}

let data = {
    id: 1, // 主键 id
    name: 'test',
    age: '18',
}

addItem(data) // 调用新增方法

从InddexDB库中读取数据

// 读取方法
function readItem(id) {
    // 创建事务,指定表名
    let transaction = idb.transaction(['store'])
    // 获取表对象
    let store = transaction.objectStore('store')
    // 调用 get 方法获取数据
    let requestStore = store.get(id)
    
    requestStore.onsuccess = function() {
        if (requestStore.result) {
            console.log(requestStore.result) // { id: 1, name: 'test', age: '18' }
        }
    }
}

readItem(1) // 获取主键 id 为 1 的数据

如果你觉着这样操作IndexDB觉着繁琐,可以考虑使用开源的NPM包www.npmjs.com/package/idb 来操作IndexDB,其对IndexDB做了很好的封装;