乐趣区

Web-存储技术

一、背景介绍

第一个 Web 存储的技术叫做 Cookie,它是网站的身份证。是网站为了辨别用户身份,进行 session(服务端的 session)跟踪而存储在用户本地终端上的数据,也就是说它是存在电脑硬盘上的,一个很小的 txt 类型的文件。Cookie 每次都会跟随 http 请求发送到服务端,也就是说每一个 http 请求都会带上我们的 cookie 数据,因此它存在一个安全性的问题。

cookie 本身也是有很大的局限性的,首先它很小,主流的浏览器最大支持 4096 字节,除了最大字节的限制,每个网站的 cookie 个数(也就是每一个 first 每一个域)也是有限制的,一般浏览器是 20 个。除此之外,cookie 还会默认跟随所有 http 请求发送,即使不需要使用这个 cookie 来鉴别用户但是它也是会跟随 http 请求发送的,这样就会造成一个网络资源的浪费。然后部分的浏览器还限制了总的 cookie 个数 300 个。

在 cookie 的诸多局限性下,Web Storage 应运而生。Web Storage 解决了很多问题:

比如它支持存储大量数据,支持复杂的本地数据库,而且也不会默认跟随 http 请求。Web Storage 主要是有四个:

  • SessionStorage
  • LocalStorage
  • WebSQL
  • indexedDB

二、Cookie 的简单介绍

Cookie 是 HTML4 的一个标准,它一般不需要考虑兼容。它是网站的一个身份证,服务器可以针对不同用户,做出不同的响应。cookie 存储在用户的机器上是一个纯文本,就是一个 txt 文件并不是一个脚本,它不能执行东西只负责记录。浏览器每次请求都会带上当前网站的 cookie。

Cookie 分为两种类型,一种呢是会话 cookie,也就是临时性的 cookie,退出浏览器或者是关闭即删除;

另一种叫持久 cookie,它会一直存在,存在的时间由特定的过期时间或者是有效期来决定。

Cookie 的域 Domain 决定了当前的一个 cookie 的权限,哪一个域可以使用这个 cookie。

Cookie 的路径 Path,下面一个简单的例子:

www.baidu.com                id="123456" domain="www.baidu.com"
www.baidu.com/user           id="123456" user="eric" domain="www.baidu.com" path="/user/"

www.baidu.com/search         id="123456";
www.baidu.com/user/search    id="123456" user="eric";

如上 www.baidu.com 设置了一个 id 等于 123456,domain 是 www.baidu.com,然后另外一个跟第一个一样多设置了一个 user,id 相同,但是多了一个 user=“Eric”,它的 domain 设置成了 www.baidu.com,path 就到了 user 下面。这两者设置完成之后,当我们访问 www.baidu.com/search 时百度只能拿到 id,因为 user=”Eric” 是属于 user 这个域下面的,也就是说在 search 下面是获取不到的,但是在 www.baidu.com/user/search 这个时候我们就可以获取到名叫 Eric 的 user。Path 也是一种权限的控制只是相较于域 domain 是低一级的。

Cookie 的安全 secure,如果这个属性为 TRUE,那么网站只有在 https 的请求下面才会携带当前的 cookie。

Cookie 的 HttpOnly 这个属性如果为 TRUE,那么就不允许 JavaScript 操作 cookie。

因为 cookie 是存储在客户端一个独立的文件,因此服务器是无法分辨用户和攻击者的。关于 cookie 的目的分为两种:一种是跨站点脚本攻击,一种是跨站请求伪造。

三、SessionStorage

key-value 的键值对,是 HTML5 新增的一个会话存储对象。

SessionStorage 是临时保存在同一窗口,也就是同一标签页的数据。如果当前标签页关闭了,那么 SessionStorage 也就失效了。这也是 SessionStorage 最显著的一个特点:单页标签限制。

除此之外,它还有的一些 特点 有:

  • 同源策略,也就是在同一协议,同一主机名和同一端口下的同一 tab
  • 只在本地存储,不会跟随 http 请求发送到服务器
  • 存储方式采用 key-value 键值对,这里面的 value 只能存字符串类型,如果存其他的会自动转换成字符串。
  • 存储上线限制达到了 5MB,如果当前存储超出上限新的内容会把旧的内容覆盖但不会报错。

属性

  • sessionStorage.length – 键值对数量
  • sessionStorage.key(int index) -> null
  • sessionStorage.getItem(string key) -> null
  • sessionStorage[string key]
  • sessionStorage.setItem(string key, string value)
  • sessionStorage.removeItem(string key)
  • sessionStorage.clear()

Json 对象

  • JSON.stringify()
  • JSON.parse()

四、LocalStorage

LocalStorage 也是在浏览器的 Application 下面有一个 Local Storage,它和 SessionStorage 是十分相似的,同样是 key-value 键值对,也是 HTML5 的新增存储对象,它与 SessionStorage 的特点不同之处在于没有标签页的限制和在浏览器的无痕模式下 LocalStorage 是不允许读取的,永久性的存储,然后 SessionStorage 超出限制是覆盖不会报错而 LocalStorage 超出会报错。

特点

  • 同源策略,也就是在同一协议,同一主机名和同一端口下的同一 tab
  • 没有标签页的限制
  • 只在本地存储,不会跟随 http 请求发送到服务器
  • 存储方式采用 key-value 键值对,这里面的 value 只能存字符串类型,如果存其他的会自动转换成字符串。
  • 存储上线限制达到了 5MB,如果当前存储超出上限会报错。
  • 无痕模式下不可读取
  • 永久性存储

属性

  • sessionStorage.length – 键值对数量
  • sessionStorage.key(int index) -> null
  • sessionStorage.getItem(string key) -> null
  • sessionStorage[string key]
  • sessionStorage.setItem(string key, string value)
  • sessionStorage.removeItem(string key)
  • sessionStorage.clear()

注意事项:LocalStorage 和 SessionStorage 在 web view 是不可靠的,web view 指的是在开发混合 APP 的时候使用了浏览器来实现我们的 APP,这个时候是不可靠的,因为在浏览器崩溃的情况下数据可能没有存进去。

另外一个在 IOS 浏览器中不可重复 setItem,如果重复会报错,然后这个时候我们需要先 removeItem 再添加 item。

监听 storage 的变化

监听 storage 包括 SessionStorage 和 LocalStorage。然后这里需要提到两个概念:同源和监听同源网页。

  • 同源:协议、域名、端口三者相同,同源的情况下我们可以共享 SessionStorage 和 LocalStorage。
    同源策略还禁止不同源执行任何脚本。

    http://localhost:63342/simpleApp/app/index.html#/
    (协议)   (域名)   (端口)         (路径)
  • 监听同源网页,但是同一网页是无效的

    window.addEventListener("storage", function (event) {console.log(event.key);
        console.log(event.oldValue);
        console.log(event.newValue);
        console.log(event.url);
        console.log(event.storageArea);
    });
    

(任何一个技术的产生都是为了解决一系列的问题)

五、IndexedDB

IndexedDB 背景

  • Storage(Storage 指的是 SessionStorage 和 LocalStorage)不适合存储大量的数据
  • Storage 不能提供搜索功能
  • Storage 不能建立索引,存储的内容也比较少
  • IndexedDB 扩大了 web 存储的容量,可以达到 250MB 以上

基本概念

首先它是一个 NoSQL,也就是一个非关系型数据库。MySQL 和 Oracle 都是关系型数据库。意思就是说如果建立了两个表在关系型数据库里面我们可以通过一个外链把多个表联系起来,但是 NoSQL 不行,在 NoSQL 里面如果想要多个表关联起来,我们只能手动的在关联的表里面添加上需要关联的另外一个或多个表的 id。这是 NoSQL 与 MySQL 两者之间的一个区别。

IndexedDB 的特点也是和 Storage 是一样的:

  • 键值对储存,但是允许所有类型,不允许主键重复报错
  • 是一个异步操作,不阻塞浏览器线程
  • 支持事务,事务是 SQL 数据库的一个概念,也就是说我们进行任何的增删改查都要在某一个事务下面进行,提供了一个回滚功能,一系列操作有一步失败, 数据库回滚到事务发生之前的状态,这样为了避免操作中途出现失败,影响整个数据库的状态
  • 同源限制
  • 支持二进制储存

IndexedDB 几个基本概念:

  • IDBDatabase – 数据库
  • IDBObjectStore – 对象仓库
  • IDBIndex – 索引
  • IDBTransaction – 事务
  • IDBRequest – 操作请求
  • IDBCursor – 指针
  • IDBKeyRange – 主键集合

IndexedDB 浏览器兼容

IDBDatabase

IDB 是 IndexedDB 的缩写,它呢就是数据库,数据库也叫作数据的一个容器。每一个源(同源策略)可以建立很多数据库。Database 有一个版本的概念,版本对应着数据库表,同一时刻只能存在一个版本。比如:新增一个表,然后我们需要把 database 的版本加一,表里面要新增一列,这时同样需要把数据库版本加一。

注意:1 同一时刻只能有一个版本存在 2 修改数据库结构只能通过升级数据库版本

  • 打开数据库

      /* databaseName 不存在则创建 */
      /* version 为整数, 新建时为 1 */
      
      let database;
      let userStore;
      const request = window.indexedDB.open(databaseName, version);
      
      /* 成功打开数据库 */
      request.onsuccess = event => {database = request.result;}
      
      /* 打开数据库失败 */
      request.onerror = error => {console.log(error);
      }
      
      /* 版本号大于当前数据库版本 */
      request.onupgradeneeded = event => {database = event.target.result;}

注意:如果在打开数据库时,数据库不存在,将会新建一个

IDBObjectStore(数据库表)

创建表,最好是在 upgradeneeded 下执行;在创建数据库表的时候需要指定主键,主键代表了唯一的标识,比如 keyPath:‘id’;如果不指定主键,我们可以指定一个 autoIncrement:true,自增的一个概念,也就是不指定主键数据库会自动添加主键而且这个主键就是数字,依次递增的。

const createStore = () => {
    // 如果当前的 objectStoreNames.contains 包含 user,如果不包含 user 这个表,然后就用这个 database.createObjectStore 创建了一个表,这个表的名字就叫做 user,然后主键就是下面的 id
      if(!db.objectStoreNames.contains('user')) {userStore = database.createObjectStore('user', { keyPath: 'id'});
      }
}

指定索引:

const createStore = () => {if(!database.objectStoreNames.contains('user')) {userStore = database.createObjectStore('user', { keyPath: 'id'});
        userStore.createIndex('name', 'name', { unique: true});
    }
}

IDBTransaction(事务)

创建完之后需要往里面添加数据,添加数据我们就需要使用到事务。

事务涉及到数据库的增删改查,它有三个 状态

  • complete
  • error
  • abort

属性

  • IDBTransaction.db 当前数据库
  • IDBTransaction.mode 模式,使用模式分为 readonly 和 readwrite
  • IDBTransaction.objectStoreNames 当前数据库涉及到的哪几个数组表
  • IDBTransaction.error 回调

数据库的基本操作:增删改查以及清空。

新增数据(add)

分为两种情况:一种是使用自增的数据库的 id 或者是自增的一个键值,如果已经创建主键,那么新增必须包含主键和另一种已创建主键但主键不可重复。

const add = () => {
    /* 创建事务 */
    /* 使用某个数据库 */
    /* add 新增 */
    transactionRequest = database.transaction(['user'], 'readwrite')
        .objectStore('user')
        .add({id: 100, name: 'Eric', age: 28, email: 'Ericlee00@163.com'});

    /* 成功 */
    transactionRequest.onsuccess = event => {console.log('数据写入成功', event);
    };

    /* 失败 */
    transactionRequest.onerror = error => {console.log('数据写入失败', error);
    }
}

读取数据(get)

const read = () => {
      /* 创建事务 */
      transaction = database.transaction(['user']);
      /* 选择数据库表 */
      table = transaction.objectStore('user');
      /* 读取数据 */
    transactionRequest = table.get(2);

    /* 成功 */
    transactionRequest.onerror = event => {console.log('数据读取失败', event);
    };

    /* 失败 */
      transactionRequest.onsuccess = event => {if (transactionRequest.result) {console.log('数据读取成功', transactionRequest.result);
          } else {console.log('未读取到数据');
          }
      };
}

更新数据(put)

更新不存在的数据时会新建,也就是说在新增数据时如果相同,往往会出错,但是在更新数据时不会出错。如果数据不存在就会新建,如果存在就会一直更新。

const update = () => {transactionRequest = database.transaction(['user'], 'readwrite')
          .objectStore('user')
          .put({id: count, name: 'David', age: 35, email: 'David@xiakedao.com'});

      transactionRequest.onsuccess = function (event) {console.log('更新数据成功', event);
      };

      transactionRequest.onerror = error => {console.log('更新数据失败', error);
      }
  }

删除数据(delete)

const delete = () => {transactionRequest = database.transaction(['user'], 'readwrite')
          .objectStore('user')
          .delete(2);

      transactionRequest.onsuccess = function (event) {console.log('删除数据成功', event);
      };

      transactionRequest.onerror = error => {console.log('删除数据失败', error);
      }
  }

清空数据(clear)

IDBCursor(指针)

提供了一种遍历数据的可能。

const readAll = () => {table = database.transaction('user').objectStore('user');

      table.openCursor().onsuccess = () => {
          let cursor = event.target.result;

          if (cursor) {console.log('数据遍历', cursor);
          cursor.continue();} else {console.log('数据遍历完成');
          }
      };
  }

关闭 IndexedDB 数据库连接

const closeDataBase = () => {database.close();
}

删除 IndexedDB 数据库前,须先关闭数据库连接

const deleteDataBase = () => {indexedDB.deleteDatabase('first_database');
}

六、WebSQL

基本概念:并不是 HTML5 的规范,只能算是一个独立的规范;使用 WebSQL 是完完全全的 SQL 语句,使用 SQL 语句来操作客户端数据库;它一共有三个比较重要的概念,分别是:openDatabase 打开数据库,可以是使用现有数据库或者新建数据库;transaction 事务,所有的数据库都支持事务;executeSql 执行 SQL 语句。

openDatabase(打开数据库)

相比于 IndexedDB 的概念稍微多一点,主要是有数据库名称、版本号(在 IndexedDB 里面版本号都是整数,但是在 WebSQL 里面它可以是小数)、描述文本(介绍数据库是干什么的)、数据库大小和创建回调(function,只在第一次创建的时候才会调用)。

const database = openDatabase('my_database', '1.0', 'first', 2 * 1024 * 1024, function() {});

Transaction(事务)

  • 创建表

    const createTable = () => {database.transaction(function (content) {content.executeSql('CREATE TABLE IF NOT EXISTS USER (id unique, name)');
          });
    }
  • 添加数据

    const addData = () => {database.transaction(function (content) {content.executeSql('INSERT INTO USER (id, name) VALUES (1,"Eric")');
          });
    }
  • 查询数据

      const searchData = () => {database.transaction(function (content) {content.executeSql('SELECT * FROM USER');
            });
      }
  • 更新数据

      const updateData = () => {database.transaction(function (content) {content.executeSql('UPDATE USER SET name=\'David\'WHERE id=1');
            });
      }
  • 删除数据库表

      const deleteDataBase = () => {database.transaction(function (content) {content.executeSql('DROP TABLE USER');
            });
        }
退出移动版