随着前端技术的发展,存储变的越来越重要,就目前来看,浏览器主要支持三类存储:Cookie、Storage、IndexedDB,下面分别介绍这三类存储方式。

一、Cookie

1.1 定义

Cookie是一个保存在浏览器中的简单的文本文件,该文件与特定的Web文档关联在一起,保存了该浏览器访问这个Web文档时的信息,当浏览器再次访问这个Web文档时这些信息可供该文档使用。(HTTP是无状态的协议,即HTTP协议本身不对请求和响应之间的通信状态进行保存,为了实现期望的保存状态功能,引入了cookie技术)

1.2 Cookie组成

在了解Cookie组成之前先了解一下Cookie的整个请求流程,这个流程分为类:一类是没有Cookie信息状态下的请求,另一类是存有Cookie状态下的请求。

通过上面的流程图可以看出,Cookie是在服务端生成的,经过查询资料了解到其是在从服务端发送的响应报文内的一个叫做Set-Cookie的首部字段信息,响应报文中有该首部字段则通知客户端保存Cookie,则Cookie的组成则跟Set-Cookie可以设置哪些值相关,目前主要有以下几类:

NAME=VALUE

Cookie的名称和值,其中NAME是唯一标识cookie的名称,不区分大小写;VALUE是存储在Cookie里的字符串值,该值必须经过URL编码。

Domain=域名

Cookie有效的域,发送到这个域的所有请求都会包含对应的Cookie。(若不指定则默认为创建Cookie的服务器的域名)

Path=PATH

请求URL中包含这个路径才会把Cookie发送到服务器(若不指定则默认为文档所在的文件目录)

Expires=DATE

Cookie的有效期,默认情况下,浏览器会话结束后会删除所有cookie。

Secure

设置后仅在HTTPS安全通信时才会发送Cookie

HttpOnly

设置后只能在服务器上读取,不能再通过JavaScript读取Cookie

const express = require('express'); 
 
const app = express(); 
 
app.get('/', (req, res) => { 
    res.cookie('myCookie', 'myCookie', { 
        expires: new Date(Date.now() + 900000), 
        secure: true, 
        httpOnly: true 
    }); 
    res.send('get请求已经被处理'); 
}) 
app.listen(8090, () => { 
    console.log('8090端口已经启动!!!'); 
});

通过请求 http://127/.0.0.1:8090 来看看其结果:

第一次返回的Cookie结果

2. 后续请求所带的Cookie信息

1.3 Cookie特点

  • 每个Cookie不超过4096字节;

  • 每个域中Cookie个数有限制,就拿最新版来说:IE和Edge不超过50个;Firefox不超过150个;Opera不超过180个;Safari和Chrome没有限制;

  • Cookie超过单个域的上限,浏览器会删除之前设置的Cookie;

  • 创建的Cookie超过最大限制,该Cookie会被静默删除;

  • 可设置失效时间,没有设置则会话结束会删除Cookie;

  • 每个请求均会携带Cookie,若Cookie过来会带来性能问题;

  • 受同源策略限制

1.4 Cookie的操作

Cookie存储到浏览器端之后仍然可以对其进行读、写、删除,由于js对Cookie操作的支持并不是很友好,所以需要进行一些简单的封装。

class CookieUtil { 
    // 获取Cookie中的对应属性 
    static get(name) { 
        const cookies = document.cookie; 
        const cookiesArr = cookies.split(';'); 
        for (let index = 0; index < cookiesArr.length; index++) { 
            const presentCookieArr = cookiesArr[index].split('='); 
            if (presentCookieArr[0] === name) { 
                return presentCookieArr[1]; 
            } 
        } 
 
        return null; 
    } 
 
    // 设置对应的Cookie值 
    static set(name, value, expires, path, domain, secure) { 
        let cookieText = `${name}=${value}`; 
        if (expires instanceof Date) { 
            cookieText += `; expire=${expires.toGMTString()}`; 
        } 
        if (path) { 
            cookieText += `; path=${path}`; 
        } 
        if (domain) { 
            cookieText += `; domain=${domain}`; 
        } 
        if (secure) { 
            cookieText += `; secure`; 
        } 
        document.cookie = cookieText; 
    } 
 
    // 删除对应的Cookie 
    static deleteCookie(name) { 
        CookieUtil.set(name, '', new Date(0)); 
    } 
}

二、Web Storage

Web Storage的目的是解决通过客户端存储不需要频繁发送回服务器的数据时使用cookie的问题,其提供了cookie之外的存储会话数据的途径和跨会话持久化存储大量数据的机制,其主要有两个对象:localStorage和sessionStorage,localStorage是永久存储机制,sessionStorage是跨会话的存储机制。

2.1 sessionStorage

sessionStorage是跨会话的存储机制,具有以下特点:

  1. sessionStorage对象值存储会话数据,其生命周期会存储到浏览器关闭。(在该过程中刷新页面其数据不受影响)

  2. 浏览器在实现存储写入时使用同步阻塞方式,数据会被立即提交到存储。

  3. 独立打开同一个窗口同一个页面或一个Tab,sessionStorage也是不一样的。

  4. 存储空间大小限制为每个源不超过5M。

// 使用方法存储数据 
sessionStorage.setItem('sessionName1', 'value1'); 
// 使用属性存储数据 
sessionStorage.sessionName2 = 'value2'; 
 
// 使用方法取得数据 
const sessionValue1 = sessionStorage.getItem('sessionName1'); 
console.log('sessionValue1的值为:', sessionValue1); 
// 使用属性取得数据 
const sessionValue2 = sessionStorage.sessionName2; 
console.log('sessionValue2的值为:', sessionValue2); 
 
// 循环遍历sessionStarage 
for (let index = 0; index < sessionStorage.length; index++) { 
    // 使用key()方法获得指定索引处的名称 
    const key = sessionStorage.key(index); 
    const value = sessionStorage.getItem(key); 
    console.log('循环遍历结果:', key, value); 
} 
 
// 使用方法删除值 
sessionStorage.removeItem('sessionName1'); 
// 使用delete删除值 
delete sessionStorage.sessionName2; 
 
// 使用clear()方法清空sessionStorage 
sessionStorage.clear();

2.2 localStorage

localStorage是永久存储机制,具有以下特点:

  1. 生命周期是永久的,除非被清除,否则永久保存。

  2. 存储空间大小限制为每个源不超过5M。

  3. 受同源策略限制。

  4. 浏览器存储时采用同步存储方式。

// 使用方法存储数据 
localStorage.setItem('localName1', 'value1'); 
// 使用属性存储数据 
localStorage.localName2 = 'value2'; 
 
// 使用方法取得数据 
const localValue1 = localStorage.getItem('localName1'); 
console.log('localValue1的值为:', localValue1); 
// 使用属性取得数据 
const localValue2 = localStorage.localName2; 
console.log('localValue2的值为:', localValue2); 
 
// 循环遍历localStarage 
for (let index = 0; index < localStorage.length; index++) { 
    // 使用key()方法获得指定索引处的名称 
    const key = localStorage.key(index); 
    const value = localStorage.getItem(key); 
    console.log('循环遍历结果:', key, value); 
} 
 
// 使用方法删除值 
localStorage.removeItem('localName1'); 
// 使用delete删除值 
delete localStorage.localName2; 
 
// 使用clear()方法清空localStorage 
localStorage.clear();

三、IndexedDB

3.1 IndexedDB整个结构

对于整个IndexedDB为上述图中所示:

  • 一个域名下可以包含多个数据库;

  • 一个数据库中包含多个对象仓库,就类似于Mysql一个库中有多张表一样。

  • 每个对象仓库中包含多条数据记录。

3.2 主要特点

IndexedDB是浏览器中存储结构化数据的一个方案,其设计几乎是完全异步的,主要有以下特点:

键值对存储

在对象仓库中,数据以“键值对”形式保存,每个数据记录都有独一无二的主键。

异步

IndexedDB操作时不会锁死浏览器,用户依然可以进行其它操作。

支持事务

一些列操作步骤之中只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。

受同源策略限制

只能访问自身域名下的数据库,不能跨域访问数据库。

存储空间大

每个源都有存储空间的限制,而且这个限制跟浏览器有关,例如Firefox限制每个源50MB,Chrome为5MB。

支持二进制存储

不仅可以存储字符串,还可以存储二进制数据(ArrayBuffer和Blob)

3.3 数据库操作

IndexedDB像很多其它数据库一样有很多操作,下面就通过实战的方式一起了解这些操作。

3.3.1 初始化数据库

第一步是初始化数据库,传入创建的数据库名和版本,获取对应的数据库操作实例。

class IndexedDBOperation { 
  constructor(databaseName, version) { 
      this.atabaseName = databaseName; 
      this.version = version; 
      this.request = null; 
      this.db = null; 
  } 
 
  // 数据库初始化操作 
  init() { 
      this.request = window.indexedDB.open(this.databaseName, this.version); 
      return new Promise((resolve, reject) => { 
          this.request.onsuccess = event => { 
              this.db = event.target.result; 
              console.log('数据库打开成功'); 
              resolve('success'); 
          }; 
          this.request.onerror = event => { 
              console.log('数据库打开报错'); 
              reject('error'); 
          }; 
          this.request.onupgradeneeded = event =>{ 
              this.db = event.target.result; 
              console.log('数据库升级'); 
              resolve('upgradeneeded'); 
          }; 
      }); 
  } 
}

3.3.2 对象仓库操作

数据是在对象仓库中存储的,创建好数据库后则需要创建所需的数据仓库

class IndexedDBOperation { 
  // …… 
  // 创建数据仓库 
  createObjectStore(objectStoreName, options) { 
      let objectStore = null; 
      if (!this.db.objectStoreNames.contains(objectStoreName)) { 
          objectStore = this.db.createObjectStore(objectStoreName, options); 
      } 
      return objectStore; 
  } 
}

3.3.3 数据操作

不管是关系型数据库还是非关系型数据库,CURD肯定是必不可少的,谁让我们是“CURD工程师”呢!!!

class IndexedDBOperation { 
  // …… 
  // 新增内容 
  add(objectStore, content) { 
      objectStore.add(content); 
  } 
   
  // 获取内容 
  get(objectStore, id) { 
      const request = objectStore.get(id); 
      return new Promise((resolve, reject) => { 
          request.onsuccess = resolve; 
          request.onerror = reject; 
      }); 
  } 
   
  // 更新内容 
  update(objectStore, content) { 
      const request = objectStore.put(content); 
      request.onsuccess = event => { 
          console.log('更新成功'); 
      }; 
      request.onerror = event => { 
          console.log('更新失败'); 
      }; 
  } 
   
  // 删除内容 
  remove(objectStore, deleteId) { 
      const request = objectStore.delete(deleteId); 
      request.onsuccess = event => { 
          console.log('删除成功'); 
      }; 
      request.onerror = event => { 
          console.log('删除失败'); 
      }; 
  } 
}


3.3.4 遍历内容

提到IndexedDB数据库,不得不提其的游标操作。

class IndexedDBOperation {   // ……   // 打印全部数据   printAllDataByCursor(objectStore) {       const cursorRequest = objectStore.openCursor();       cursorRequest.onsuccess = event => {           const cursor = event.target.result;           if (cursor) {               console.log(`利用游标打印的内容,id为${cursor.key}, 值为${cursor.value}`);               // 移动到下一条记录               cursor.continue();           }       };   } }

3.3.5 调用代码

上面写了一个数据库的类,但是仍然不知道怎么调用呀,下面就用一个demo讲述其调用。

const indexedDBOperation = new IndexedDBOperation('dbName1', 1); 
indexedDBOperation 
.init() 
.then(type => { 
    const objectStoreName = 'testObjectStore'; 
    if (type === 'upgradeneeded') { 
        indexedDBOperation.createObjectStore(objectStoreName, { 
            keyPath: 'id' 
        }); 
    } 
    const transaction = indexedDBOperation.db.transaction([objectStoreName], 'readwrite'); 
    const objectStore = transaction.objectStore(objectStoreName); 
 
    indexedDBOperation 
    .get(objectStore, 1) 
    .then(event => { 
        if (event.target.result) { 
            indexedDBOperation.update(objectStore, { 
                id: 1, 
                name: '执鸢者+纸鸢' 
            }); 
            console.log('数据库中已经存在', event.target.result, ',则进行更新操作'); 
        } 
        else { 
            indexedDBOperation.add(objectStore, { 
                id: 1, 
                name: '执鸢者' 
            }); 
            console.log('数据库中不存在,则进行添加'); 
        } 
    }) 
    .catch(console.log); 
 
    indexedDBOperation.printAllDataByCursor(objectStore); 
 
    transaction.onsuccess = event => { 
        console.log('事务操作成功'); 
    }; 
    transaction.onerror = event => { 
        console.log('事务操作失败'); 
    }; 
    transaction.oncomplete = event => { 
        console.log('整个事务成功完成'); 
    } 
}) 
.catch(console.log);

参考文献

  • 浏览器数据库 IndexedDB 入门教程

  • javascript高级程序设计4

  • IndexedDB API