前端数据库:indexedDB

208 阅读6分钟

我们比较常见的前端存储方案有:cookiesessionStoragelocalStorage这三种

他们都有一个共同的特点:就是可存储空间小

  • cookie:大修改只有4kb
  • sessionStorage:大约有5M,只能在当前页面共享,而且关闭页面就会清除数据
  • localStorage:同样有5M,永久性存储

但是,当我们想要在本地缓存大数据的时候,以上三个方法显然都不能满足,这时候就可以使用indexedDB

1、初识indexedDB

1.1 什么是indexedDB

indexedDBhtml5提供的一种存储结构化数据的方案,可以简单理解为是类似于MySQL的前端数据库,但是它是属于非关系型的数据库

之所以能称之为数据库,那是因为:

  1. 存储空间大,存储空间为磁盘空间的50%
  2. 提供一套API,方便对象的存储和获取,同时支持查询和搜索
  3. 永久性存储

注意:

  • 当存储空间操作限制时,会发起一个源回收的的过程,将整个数据库删除,不存在只删除一部分
  • 数据其实就存储在当前浏览器所在的文件夹,一般是在C盘:
    • C:\Users\ASUS\AppData\Local\Google\Chrome\User Data\Default\IndexedDB

1.2 indexedDB的特点

  • 非关系型数据库
  • 传统的数据库如MySQL,他们是以一张二维的表来存储数据的
  • indexedDB属于非关系统数据库,它是以键值对来存储数据的
  • 异步操作
  • indexedDB大多数API都是异步的,操作数据库的同时不会阻塞其他操作
  • localStorage是属于同步操作
  • 支持事务
  • indexedDB支持事务,操作数据库的过程中,有一步发生错误,那么整个事物都会取消,回滚到事务发生之前,不存在只修改了某一部分
  • 同源策略
  • localStorage类似,indexedDB也支持同源策略
  • 存储空间大、持久化存储

2、操作indexedDB

2.1 有这么几个概念

  1. 仓库
  2. 事务
  3. 索引
  4. 游标

1)仓库objectStore

  • indexedDB没有表的概念,但是我们在操作indexedDB,都得通过createObjectStore来创建一个仓库,我们可以把这个仓库看作是表

2)事物transaction

  • 简单说,就是操作失败,会回滚到你操作之前的状态
  • 进行每一步操作之前都要使用transaction来创建一个事物,基于这个事务进行操作

3)索引index

  • indexedDB数据库的每条数据都可以有索引,当我们在创建仓库的时候可以同时创建索引
  • 后续操作可以通过索引来筛选,加快查找速率

4)游标cursor

  • indexedDB中的游标就好比指针,当查询符合某一个条件的数据时,就可以使用图标一条一条往下查找
  • 类似for循环,想要获取所有数据即可通过游标来实现

indexedDB中,想获取数据就只能通过主键(自定义的键)、索引游标来查询

2.2 indexedDB的使用

使用步骤:

1. 创建数据库

通过indexedDB.open()方法,接收两个参数

  • 数据库名称
  • 指定版本号

这时就会发送一个请求,若数据存在则打开数据库;否则创建并打开数据库,同时会返回一个实例,可以在实例上添加onerroronsuccess事件,监听数据库的打开状态

直接看代码:

let db = null;
const dbName = 'DBTest';
const version = 1;

// 创建一个数据库
const request = indexedDB.open(dnName, vertion)

// 添加处理程序
request.onsuccess = (event) => {
  db = event.target.result
}
request.onerror = (event) => {
  console.log('呜呼---> ', event)
}

创建数据库之后,就会触发onupgradeneeded事件,在这个事件中我们可以创建仓库

request.onupgradeneeded = (event) => {
  db = event.target.result

  // 如果该仓库已经存在,删除
  if (db.objectStoreNames.contains('users')) {
    db.deleteObjectStore('users')
  }

  // 创建users仓库
  db.createObjectStore('users', { keyPath: 'id' });
}

createObjectStore()接收两个参数

  • 第一个参数:仓库名称
  • 第二个参数:keyPath是指定主键

啥意思呢?就是仓库好比一个数组,里面存储着一个个的对象:[{id: ...}, {id: ...}, {id: ...}, ...],这个keyPath就是指定每个对象的属性名,即键名

经过以上步骤,你大概就可以在浏览器中看到以上的结果,说明数据库创建成功,接下来就是可以进行CRUD

2. 创建事务

创建对象存储后,就需要创建事务了,后续的操作都是通过事物来完成的

  • 通过调用transaction()来创建事物
const transaction = db.transaction()

它接收两个参数:

  1. 指定要访问的store仓库的名称 —— 字符串 或 字符串数组
  2. 访问的方式:readonly(只读,默认)、readwrite读写、versionchange版本改变
// 对users仓库进行读写
const transaction = db.transaction('users', 'readwrite)

// 对多个store操作的话 使用数组
const transaction = db.transaction(['A', 'B'], 'readwrite)

注意:transaction参数为空,默认对所有store都有只读权限(建议每次都指定store

3. 添加数据

有了事务那么就可以进行操作了

  • 添加数据使用add()方法
const addData = (db, storeName, data) => {
  const transaction = db.transaction(storeName, 'readwrite') // 创建事务
  // objectStore() 方法获取对应的store
  const store = transaction.objectStore(storeName)
  const request = store.add(data)  // 创建新的请求对象

  request.onsuccess = (event) => {
    console.log('添加成功---> ', event.target.result);
  }

  request.onerror = (event) => {
    console.log('添加失败---> ', event);
  }
}

4. 获取数据

通过get() 方法获取数据

// 通过主键key获取数据
const getData = (db, storeName, key) => {
  const transaction = db.transaction(storeName, 'readonly') // 创建事务
  const store = transaction.objectStore(storeName)  // 获取仓库
  const request = store.get(key)  // 创建新的请求对象

  request.onsuccess = (event) => {
    console.log('获取成功---> ', event.target.result);
  }
  request.onerror = (event) => {
    console.log('获取失败---> ', event);
  }
}

5. 更新数据

更新数据使用put()方法

// 更新data数据
const putData = (db, storeName, data) => {
  const transaction = db.transaction(storeName, 'readwrite') // 创建事务
  const store = transaction.objectStore(storeName)  // 获取仓库
  const request = store.put(data)  // 创建新的请求对象

  request.onsuccess = (event) => {
    console.log('更新成功---> ', event.target.result);
  }
  request.onerror = (event) => {
    console.log('更新失败---> ', event);
  }
}

put()add()很相似,都是接收data数据,然后存储到store中;不同的是如果store存在同名的主键,add()会报错,而put()方法会重写该数据

6.删除数据

delete()方法用于删除数据

// 删除主键为key的数据
const deleteData = (db, storeName, key) => {
  const transaction = db.transaction(storeName, 'readwrite') // 创建事务
  const store = transaction.objectStore(storeName)  // 获取仓库
  const request = store.delete(key)  // 创建新的请求对象

  request.onsuccess = (event) => {
    console.log('删除成功---> ', event);
  }
  request.onerror = (event) => {
    console.log('删除失败---> ', event);
  }
}

7. 游标查询

indexedDB中的游标就有一个for循环的功能,可以获取、过滤store中的数据

  • 通过openCursor()方法来创建一个游标
const cursor = (db, storeName, key) => {
  const transaction = db.transaction(storeName, 'readwrite') // 创建事务
  const store = transaction.objectStore(storeName)  // 获取仓库
  const request = store.openCursor()  // 创建新的请求对象

  request.onsuccess = (event) => {
    const cursor = event.target.result   // 获取第一个游标(指针)
    // 切记,永远要检查
    if (cursor) {
      // 执行一些操作
      console.log('当前的数据--> ', cursor);

      cursor.continue()  // 移动到下一个
    }
  }
  request.onerror = (event) => {
    console.log(event);
  }
}

2.3 使用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>indexedDB</title>
</head>
<body>
  <script>
    var db = null;
    var storeName = 'users';
    var databaseName = 'DBTest';
    var version = 1;

    const request = indexedDB.open(databaseName, version)
    request.onerror = (err) => {
      console.log(err);
    }
    // indexDB打开成功
    request.onsuccess = (event) => {
      db = event.target.result
      console.log('db--> ', db);
    }

    request.onupgradeneeded = (event) => {
      db = event.target.result

      // 如果该仓库已经存在,删除
      if (db.objectStoreNames.contains(storeName)) {
        db.deleteObjectStore(storeName)
      }

      // 创建该仓库
      db.createObjectStore(storeName, { keyPath: 'id' });
    }

    const addData = (db, storeName, data) => {
      const transaction = db.transaction(storeName, 'readwrite') // 创建事务
      // objectStore() 方法获取对应的store
      const store = transaction.objectStore(storeName)
      const request = store.add(data)  // 创建新的请求对象

      request.onsuccess = (event) => {
        console.log('添加成功---> ', event.target.result);
      }

      request.onerror = (event) => {
        console.log('添加失败---> ', event);
      }
    }

    // 通过主键key获取数据
    const getData = (db, storeName, key) => {
      const transaction = db.transaction(storeName, 'readonly') // 创建事务
      const store = transaction.objectStore(storeName)  // 获取仓库
      const request = store.get(key)  // 创建新的请求对象

      request.onsuccess = (event) => {
        console.log('获取成功---> ', event.target.result);
      }
      request.onerror = (event) => {
        console.log('获取失败---> ', event);
      }
    }

    // 更新data数据
    const putData = (db, storeName, data) => {
      const transaction = db.transaction(storeName, 'readwrite') // 创建事务
      const store = transaction.objectStore(storeName)  // 获取仓库
      const request = store.put(data)  // 创建新的请求对象

      request.onsuccess = (event) => {
        console.log('更新成功---> ', event.target.result);
      }
      request.onerror = (event) => {
        console.log('更新失败---> ', event);
      }
    }
    
    // 删除主键为key的数据
    const deleteData = (db, storeName, key) => {
      const transaction = db.transaction(storeName, 'readwrite') // 创建事务
      const store = transaction.objectStore(storeName)  // 获取仓库
      const request = store.delete(key)  // 创建新的请求对象

      request.onsuccess = (event) => {
        console.log('删除成功---> ', event);
      }
      request.onerror = (event) => {
        console.log('删除失败---> ', event);
      }
    }

    // 删除主键为key的数据
    const cursor = (db, storeName, key) => {
      const transaction = db.transaction(storeName, 'readwrite') // 创建事务
      const store = transaction.objectStore(storeName)  // 获取仓库
      const request = store.openCursor()  // 创建新的请求对象

      request.onsuccess = (event) => {
        const cursor = event.target.result   // 获取第一个游标(指针)
        // 切记,永远要检查
        if (cursor) {
          // 执行一些操作
          console.log('当前的数据--> ', cursor);

          cursor.continue()  // 移动到下一个
        }
      }
      request.onerror = (event) => {
        console.log(event);
      }
    }

    const data1 = {  
      id: 1,
      name:'张一',
      age:  1,
      email:'zhangsan@example.com' 
    }
    const data2 = {  
        id: 2,
        name:'张二',
        age:  2,
        email:'zhangsan@example.com' 
    }
    const data3 = {  
        id: 1,
        name:'张三',
        age:  3,
        email:'zhangsan@example.com' 
    }

    // 注意:我们设置主键为id,那么添加的数据中必须要有id这个键
    setTimeout(() => {
      addData(db, storeName, data1)  // 添加
      getData(db, storeName, 1)   // 获取
      putData(db, storeName, data2)   // 更新(此时数据库不存在该数据,会直接添加)
      putData(db, storeName, data3)   // 更新以存在的数据
      deleteData(db, storeName, 1)   // 删除,通过主键
    }, 500)
  </script>
</body>
</html>
  • 由于创建indexedDB的操作为异步的,故需要将CRUD的操作变成异步的
  • 否则在创建事务的时候,db还未被赋值会导致报错
  • 更好的做法是使用Promise

执行上面的操做,就可以看到:

3、总结

indexedDB是一个非关系型的前端数据库,所以操作都是基于事务来进行的,但某一步出错,会回滚到操作事务之前,并且操作都是异步的,不会影响到浏览器的其他操作

操作步骤:

打开数据库 -> 创建一个仓库 -> 创建事务、基于事务获取仓库 -> add/get/delete/put