JavaScript IndexedDB

摘要:在本教程中,您将了解 IndexedDB 以及如何使用它在浏览器中持久存储数据。

什么是 indexedDB

IndexedDB 是一个内置于浏览器的大型对象存储

IndexedDB 允许您使用键值对持久存储数据。

值可以是任何JavaScript 类型,包括布尔值数字字符串未定义、空值、日期、对象数组正则表达式、blob 和文件。

为什么选择 indexedDB

IndexedDB 允许您创建可以在线和离线工作的 Web 应用程序。

它适用于存储大量数据且不需要持久互联网连接的应用程序。

例如,Google Docs 使用 IndexedDB 在浏览器中存储缓存的文档,并定期与服务器同步。这使 Google Docs 能够提高性能,同时增强用户体验。

您还会发现其他类型大量使用 IndexedDB 的应用程序,例如在线笔记、测验、待办事项列表、代码沙箱和 CMS。

IndexedDB 结构

下图说明了 IndexedDB 的结构

数据库

数据库是 IndexedDB 的最高级别。数据库包含一个或多个对象存储。

IndexedDB 可以包含一个或多个数据库。通常,您将为每个 Web 应用程序创建一个数据库。

对象存储

对象存储是一个存储数据和相关索引的存储桶。从概念上来说,它等同于 SQL 数据库中的表。

对象存储包含以键值对形式存储的记录。

索引

索引允许您按对象的属性查询数据。

从技术上讲,您在对象存储上创建索引,这些索引被称为父对象存储。

例如,如果您存储联系人信息,您可能希望在电子邮件、姓氏和名字上创建索引,以便您可以通过这些属性查询联系人。

基本的 IndexedDB 概念

以下简要介绍了 IndexedDB 中的基本概念

1) IndexedDB 数据库存储键值对

localStoragesessionStorage 不同,存储在 IndexedDB 中的值可以是复杂结构,例如对象和 blob。

此外,键可以是这些对象的属性,也可以是二进制对象。

为了快速搜索和排序,您可以创建使用对象任何属性的索引。

2) IndexedDB 是事务性的

对 IndexedDB 数据库的每次读写操作都发生在事务中。

事务模型确保了数据完整性,以防用户同时在两个选项卡/窗口中打开 Web 应用程序并对同一数据库执行读写操作。

3) IndexedDB API 主要是非同步的

IndexedDB 操作是非同步的。它使用 DOM 事件来通知您操作何时完成以及结果何时可用。

4) IndexedDB 是一个 NoSQL 系统

IndexedDB 是一个 NoSQL 系统。换句话说,它不使用 SQL 来查询数据。相反,它使用返回游标的查询。然后,您可以使用游标来迭代结果集。

5) IndexedDB 遵循同源策略

源是执行代码的文档的 URL 的域、协议和端口。例如https://tutorial.javascript.ac.cn

  • 域:javascripttutorial.net
  • 协议:https
  • 端口:443

https://tutorial.javascript.ac.cn/dom/https://tutorial.javascript.ac.cn/ 是同一个源,因为它们具有相同的域、协议和端口。

但是,https://tutorial.javascript.ac.cn/https://tutorial.javascript.ac.cn/ 不是同一个源,因为它们具有不同的协议和端口

https://tutorial.javascript.ac.cnhttps://tutorial.javascript.ac.cn
协议httpshttp
端口44380

IndexedDB 遵守同源策略。这意味着每个源都有自己的数据库集。一个源不能访问其他源的数据库。

基本的 IndexedDB 操作

以下描述了 IndexedDB 数据库上的基本操作,例如

  • 打开数据库连接。
  • 将对象插入对象存储。
  • 从对象存储读取数据。
  • 使用游标迭代结果集。
  • 从对象存储中删除对象。

在打开 IndexedDB 中的数据库连接之前,让我们先创建项目结构。

1) 创建项目结构

首先,创建一个名为 indexeddb 的新文件夹。在 indexeddb 文件夹中,创建一个名为 js 的子文件夹。

其次,在 indexeddb 文件夹中创建 index.html,在 js 文件夹中创建 app.js

第三,将链接到 app.js 文件的 <script> 标签放在 index.html 文件中,如下所示

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IndexedDB</title>
</head>
<body>
    <script src="js/app.js"></script>
</body>
</html>Code language: HTML, XML (xml)

在 app.js 中,您将把所有 JavaScript 代码放在一个IIFE 中。

(function () {
   // all the code will be here  
   // ...
})();Code language: JavaScript (javascript)

1) 检查是否支持 IndexedDB

以下代码检查 Web 浏览器是否支持 IndexedDB

if (!window.indexedDB) {
    console.log(`Your browser doesn't support IndexedDB`);
    return;
}Code language: JavaScript (javascript)

由于大多数现代 Web 浏览器都支持 IndexedDB,因此这可能不再必要。

2) 打开数据库

要打开数据库连接,您可以使用 window.indexedDBopen() 方法

const request = indexedDB.open('CRM', 1);Code language: JavaScript (javascript)

open() 方法接受两个参数

  • 数据库名称 (CRM)
  • 数据库版本 (1)

open() 方法返回一个请求对象,它是 IDBOpenDBRequest 接口的实例。

当您调用 open() 方法时,它可能成功也可能失败。要处理每种情况,您可以分配相应的事件处理程序,如下所示

request.onerror = (event) => {
    console.error(`Database error: ${event.target.errorCode}`);
};

request.onsuccess = (event) => {
    // add implementation here
};Code language: JavaScript (javascript)

3) 创建对象存储

当您首次打开数据库时,将触发 onupgradeneeded 事件。

如果您第二次打开数据库,并且版本高于现有版本,则 onupgradeneeded 事件也会触发。

对于第一次,您可以使用 onupgradeneeded 事件处理程序来初始化对象存储和索引。

例如,以下 onupgradeneeded 事件处理程序创建 Contacts 对象存储及其索引。

 // create the Contacts object store and indexes
 request.onupgradeneeded = (event) => {
     let db = event.target.result;

     // create the Contacts object store 
     // with auto-increment id
     let store = db.createObjectStore('Contacts', {
         autoIncrement: true
     });

     // create an index on the email property
     let index = store.createIndex('email', 'email', {
         unique: true
     });
 };Code language: JavaScript (javascript)

工作原理。

  • 首先,从 event.target.result 中获取 IDBDatabase 实例并将其分配给 db 变量。
  • 其次,调用 createObjectStore() 方法以使用 autoincrement 键创建 Contacts 对象存储。这意味着 IndexedDB 将从一开始就生成一个自动递增的数字作为插入到 Contacts 对象存储中的每个新对象的键。
  • 第三,调用 createIndex() 方法以在 email 属性上创建索引。由于电子邮件是唯一的,因此索引也应该是唯一的。为此,您指定 createIndex() 方法的第三个参数 { unique: true }

4) 将数据插入对象存储

成功打开数据库连接后,您可以在 onsuccess 事件处理程序中管理数据。

例如,要将对象添加到对象存储,请执行以下步骤

  • 首先,打开一个新事务。
  • 其次,获取对象存储。
  • 第三,调用对象存储的 put() 方法以插入新记录。
  • 最后,在事务完成后关闭与数据库的连接。

以下 insertContact() 函数将新的联系人插入到 Contacts 对象存储中

function insertContact(db, contact) {
    // create a new transaction
    const txn = db.transaction('Contacts', 'readwrite');

    // get the Contacts object store
    const store = txn.objectStore('Contacts');
    //
    let query = store.put(contact);

    // handle success case
    query.onsuccess = function (event) {
        console.log(event);
    };

    // handle the error case
    query.onerror = function (event) {
        console.log(event.target.errorCode);
    }

    // close the database once the 
    // transaction completes
    txn.oncomplete = function () {
        db.close();
    };
}Code language: JavaScript (javascript)

要创建一个新事务,您需要调用 IDBDatabase 对象的 transaction() 方法。

您可以以两种模式之一打开事务:readwritereadonlyreadwrite 模式允许您从数据库中读取数据并向数据库写入数据,而 readonly 模式只允许您从数据库中读取数据。

如果只需要从数据库中读取数据,那么最好打开一个 readonly 事务。

在定义 insertContact() 函数后,您可以在请求的 onsuccess 事件处理程序中调用它,以插入一个或多个联系人,如下所示

request.onsuccess = (event) => {
     const db = event.target.result;

     insertContact(db, {
         email: '[email protected]',
         firstName: 'John',
         lastName: 'Doe'
     });

     insertContact(db, {
         email: '[email protected]',
         firstName: 'Jane',
         lastName: 'Doe'
     });
};Code language: JavaScript (javascript)

现在,如果您在 Web 浏览器中打开 index.html 文件,则 app.js 中的代码将执行以下操作

  • 在 IndexedDB 中创建 CRM 数据库。
  • CRM 数据库中创建 Contacts 对象存储。
  • 将两条记录插入对象存储。

如果您在 Web 浏览器上打开开发者工具,您将看到 CRM 数据库以及 Contacts 对象存储。在 Contacts 对象存储中,您将看到其中的数据,如下面的图片所示

5) 按键从对象存储读取数据

要按其键读取对象,您可以使用对象存储的 get() 方法。以下 getContactById() 函数按 id 查找联系人

function getContactById(db, id) {
    const txn = db.transaction('Contacts', 'readonly');
    const store = txn.objectStore('Contacts');

    let query = store.get(id);

    query.onsuccess = (event) => {
        if (!event.target.result) {
            console.log(`The contact with ${id} not found`);
        } else {
            console.table(event.target.result);
        }
    };

    query.onerror = (event) => {
        console.log(event.target.errorCode);
    }

    txn.oncomplete = function () {
        db.close();
    };
};Code language: JavaScript (javascript)

当您调用对象存储的 get() 方法时,它将返回一个将异步执行的查询。

由于查询可能成功也可能失败,因此您需要分配 onsuccessonerror 处理程序以处理每种情况。

如果查询成功,您将在 event.target.result 中获得结果。否则,您将通过 event.target.errorCode 获得错误代码。

以下代码在事务完成后关闭与数据库的连接

txn.oncomplete = function () {
   db.close();
};Code language: JavaScript (javascript)

实际上,只有在所有事务完成后才会关闭数据库连接。

以下代码在 onsuccess 事件处理程序中调用 getContactById() 以获取 id 为 1 的联系人

request.onsuccess = (event) => {
    const db = event.target.result;
    getContactById(db, 1);
};Code language: JavaScript (javascript)

输出

6) 按索引从对象存储读取数据

以下定义了一个名为 getContactByEmail() 的新函数,该函数使用电子邮件索引来查询数据

function getContactByEmail(db, email) {
    const txn = db.transaction('Contacts', 'readonly');
    const store = txn.objectStore('Contacts');

    // get the index from the Object Store
    const index = store.index('email');
    // query by indexes
    let query = index.get(email);

    // return the result object on success
    query.onsuccess = (event) => {
        console.log(query.result); // result objects
    };

    query.onerror = (event) => {
        console.log(event.target.errorCode);
    }

    // close the database connection
    txn.oncomplete = function () {
        db.close();
    };
}Code language: JavaScript (javascript)

工作原理。

  • 首先,从 Contacts 对象存储获取电子邮件索引对象。
  • 其次,使用索引通过调用 get() 方法读取数据。
  • 第三,在查询的 onsuccess 事件处理程序中显示结果。

以下说明如何在 onsuccess 事件处理程序中使用 getContactByEmail() 函数

request.onsuccess = (event) => {
    const db = event.target.result;
    // get contact by email
    getContactByEmail(db, '[email protected]');
};Code language: JavaScript (javascript)

输出

7) 从对象存储读取所有数据

以下展示了如何使用游标从 Contacts 对象存储中读取所有对象

function getAllContacts(db) {
    const txn = db.transaction('Contacts', "readonly");
    const objectStore = txn.objectStore('Contacts');

    objectStore.openCursor().onsuccess = (event) => {
        let cursor = event.target.result;
        if (cursor) {
            let contact = cursor.value;
            console.log(contact);
            // continue next record
            cursor.continue();
        }
    };
    // close the database connection
    txn.oncomplete = function () {
        db.close();
    };
}Code language: JavaScript (javascript)

objectStore.openCursor() 返回一个用于迭代对象存储的游标。

要使用游标迭代对象存储中的对象,您需要分配一个 onsuccess 处理程序

objectStore.openCursor().onsuccess = (event) => {
   //...
};
Code language: JavaScript (javascript)

event.target.result 返回游标。要获取数据,您可以使用 cursor.value 属性。

cursor.continue() 方法将游标前进到对象存储中下一条记录的位置。

以下代码在 onsuccess 事件处理程序中调用 getAllContacts() 函数,以显示 Contacts 对象存储中的所有数据

request.onsuccess = (event) => {
    const db = event.target.result;
    // get all contacts
    getAllContacts(db);
};Code language: JavaScript (javascript)

输出

8) 删除联系人

要从对象存储中删除记录,可以使用对象存储的 delete() 方法。

以下函数通过 ID 从 Contacts 对象存储中删除联系人

function deleteContact(db, id) {
    // create a new transaction
    const txn = db.transaction('Contacts', 'readwrite');

    // get the Contacts object store
    const store = txn.objectStore('Contacts');
    //
    let query = store.delete(id);

    // handle the success case
    query.onsuccess = function (event) {
        console.log(event);
    };

    // handle the error case
    query.onerror = function (event) {
        console.log(event.target.errorCode);
    }

    // close the database once the 
    // transaction completes
    txn.oncomplete = function () {
        db.close();
    };
}Code language: JavaScript (javascript)

你可以在 onsuccess 事件处理程序中调用 deleteContact() 函数,以删除 ID 为 1 的联系人,如下所示

request.onsuccess = (event) => {
    const db = event.target.result;
    deleteContact(db, 1);
};Code language: JavaScript (javascript)

如果运行代码,你会发现 ID 为 1 的联系人将被删除。

将所有内容整合在一起

以下是完整的 app.js 文件

(function () {
    // check for IndexedDB support
    if (!window.indexedDB) {
        console.log(`Your browser doesn't support IndexedDB`);
        return;
    }

    // open the CRM database with the version 1
    const request = indexedDB.open('CRM', 1);

    // create the Contacts object store and indexes
    request.onupgradeneeded = (event) => {
        let db = event.target.result;

        // create the Contacts object store 
        // with auto-increment id
        let store = db.createObjectStore('Contacts', {
            autoIncrement: true
        });

        // create an index on the email property
        let index = store.createIndex('email', 'email', {
            unique: true
        });
    };

    // handle the error event
    request.onerror = (event) => {
        console.error(`Database error: ${event.target.errorCode}`);
    };

    // handle the success event
    request.onsuccess = (event) => {
        const db = event.target.result;

        // insert contacts
        // insertContact(db, {
        //     email: '[email protected]',
        //     firstName: 'John',
        //     lastName: 'Doe'
        // });

        // insertContact(db, {
        //     email: '[email protected]',
        //     firstName: 'Jane',
        //     lastName: 'Doe'
        // });


        // get contact by id 1
        // getContactById(db, 1);


        // get contact by email
        // getContactByEmail(db, '[email protected]');

        // get all contacts
        // getAllContacts(db);

        deleteContact(db, 1);

    };

    function insertContact(db, contact) {
        // create a new transaction
        const txn = db.transaction('Contacts', 'readwrite');

        // get the Contacts object store
        const store = txn.objectStore('Contacts');
        //
        let query = store.put(contact);

        // handle success case
        query.onsuccess = function (event) {
            console.log(event);
        };

        // handle the error case
        query.onerror = function (event) {
            console.log(event.target.errorCode);
        }

        // close the database once the 
        // transaction completes
        txn.oncomplete = function () {
            db.close();
        };
    }


    function getContactById(db, id) {
        const txn = db.transaction('Contacts', 'readonly');
        const store = txn.objectStore('Contacts');

        let query = store.get(id);

        query.onsuccess = (event) => {
            if (!event.target.result) {
                console.log(`The contact with ${id} not found`);
            } else {
                console.table(event.target.result);
            }
        };

        query.onerror = (event) => {
            console.log(event.target.errorCode);
        }

        txn.oncomplete = function () {
            db.close();
        };
    };

    function getContactByEmail(db, email) {
        const txn = db.transaction('Contacts', 'readonly');
        const store = txn.objectStore('Contacts');

        // get the index from the Object Store
        const index = store.index('email');
        // query by indexes
        let query = index.get(email);

        // return the result object on success
        query.onsuccess = (event) => {
            console.table(query.result); // result objects
        };

        query.onerror = (event) => {
            console.log(event.target.errorCode);
        }

        // close the database connection
        txn.oncomplete = function () {
            db.close();
        };
    }

    function getAllContacts(db) {
        const txn = db.transaction('Contacts', "readonly");
        const objectStore = txn.objectStore('Contacts');

        objectStore.openCursor().onsuccess = (event) => {
            let cursor = event.target.result;
            if (cursor) {
                let contact = cursor.value;
                console.log(contact);
                // continue next record
                cursor.continue();
            }
        };
        // close the database connection
        txn.oncomplete = function () {
            db.close();
        };
    }


    function deleteContact(db, id) {
        // create a new transaction
        const txn = db.transaction('Contacts', 'readwrite');

        // get the Contacts object store
        const store = txn.objectStore('Contacts');
        //
        let query = store.delete(id);

        // handle the success case
        query.onsuccess = function (event) {
            console.log(event);
        };

        // handle the error case
        query.onerror = function (event) {
            console.log(event.target.errorCode);
        }

        // close the database once the 
        // transaction completes
        txn.oncomplete = function () {
            db.close();
        };

    }
})();Code language: JavaScript (javascript)

总结

  • IndexedDB 是 Web 浏览器中的一种大型对象存储。
  • IndexedDB 以键值对的形式存储数据。这些值可以是任何数据,包括简单和复杂数据。
  • IndexedDB 由一个或多个数据库组成。每个数据库都有一个或多个对象存储。通常,每个 Web 应用程序在 IndexedDB 中创建一个数据库。
  • IndexedDB 对于不需要持久互联网连接的 Web 应用程序非常有用,尤其是对于在线和离线都可用的应用程序。
本教程对您有帮助吗?