发布于 2026-01-06 6 阅读
0

使用 IndexedDB 构建一个基本的 Web 应用程序 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 IndexedDB 构建一个基本的 Web 应用程序

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

IndexedDB 是一款 NoSQL 数据库,您可以在所有主流浏览器上使用它来存储大量数据,并像在 MongoDB 等数据库中一样进行查询。如果您正在开发一个存储大量数据的 Web 应用或浏览器扩展程序,并且希望提供多种查询方式,那么 IndexedDB 就是您的理想之选!

在本教程中,我们将创建一个简单的、不使用任何框架的便签 Web 应用,以此概述使用 IndexedDB 时应该了解的概念。如需更深入的了解,Mozilla 开发者网络的“使用 IndexedDB”是另一个很棒的概述,我还推荐https://www.freecodecamp.org/news/a-quick-but-complete-guide-to-indexeddb-25f030425501/,它更侧重于 API 方法。

您可以在这里找到本教程的代码,关于向 IDB 代码添加测试覆盖率的本教程第 2 部分在这里

为什么要在我的Web应用程序中使用IndexedDB?

正如我前面提到的,选择 IndexedDB 而不是本地存储的原因主要有两个:

  • 没有大小限制;如果您的应用处理大量数据,超过了本地存储和会话存储所能提供的几兆字节,IndexedDB 可以让您存储大量数据。
  • 结构化存储;您可以将对象存储在 IndexedDB 对象存储中,并使用它们的字段查询它们。

这些也是将数据存储在服务器上的优势,因此如果你的项目有后端,你可以直接将数据存储在后端。但如果你正在开发一个离线优先的 Web 应用,或者一个没有后端的应用,IndexedDB 就是你技术栈的绝佳选择。例如,我正在开发一个浏览器扩展,用于生成用户标签页的图表,从而实现可视化、交互式的网页浏览历史记录。为此,我需要能够存储大量标签页,并按时间顺序检索它们,而且该应用没有 Web 后端,所以 IndexedDB 非常合适!

构建我们的数据库

好了,我们开始制作应用吧!首先,创建一个名为 indexeddb-tutorial 的文件夹,并在一个名为 indexeddb-tutorial 的文件中db.js,添加以下代码,这将创建我们的数据库!



let db;
let dbReq = indexedDB.open('myDatabase', 1);

dbReq.onupgradeneeded = function(event) {
  // Set the db variable to our database so we can use it!  
  db = event.target.result;

  // Create an object store named notes. Object stores
  // in databases are where data are stored.
  let notes = db.createObjectStore('notes', {autoIncrement: true});
}
dbReq.onsuccess = function(event) {
  db = event.target.result;
}

dbReq.onerror = function(event) {
  alert('error opening database ' + event.target.errorCode);
}


Enter fullscreen mode Exit fullscreen mode

要运行这段 JavaScript 代码,请将其放入名为 index.html 的文件中,然后在 Chrome 浏览器中打开它:



<!DOCTYPE html>
<html>
  <head><title>IndexedDB note store</title></head>
  <body>
    <div id="app"><h1>Coming soon</h1></div>
    <script src="db.js"></script>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

现在在 Chrome 浏览器中,打开开发者工具,点击应用程序标签,然后点击左侧栏中的IndexedDB,可以看到数据库已经创建好了!

这是我们的 IndexedDB 面板的屏幕截图,其中包含我们创建的数据库和对象存储。

太棒了!我们有一个名为`<database_name> ` 的数据库myDatabase,还有一个名为`<object_store_name>` 的对象存储(类似于 SQL 表或 MongoDB 中的集合)notes。但是仅仅为了创建数据库和存储就写了这么多代码,感觉有点多。这是怎么回事呢?

开头几行



let db;
let dbReq = indexedDB.open('myDatabase', 1);


Enter fullscreen mode Exit fullscreen mode

我们打开名为 myDatabase 的数据库版本 1,但indexedDB.open它不会返回数据库,而是返回一个数据库请求,因为 IndexedDB 是一个异步API。IndexedDB 代码在后台运行,所以如果我们执行类似存储数千条记录的操作,Web 应用程序的其他部分不会停止运行 JavaScript 代码,而是等待该操作完成。因此,在其余代码中,我们使用事件监听器来监听数据库何时准备就绪:



dbReq.onupgradeneeded = function(event) {
  db = event.target.result;
  let notes = db.createObjectStore('notes', {autoIncrement: true});
}


Enter fullscreen mode Exit fullscreen mode

myDatabase由于之前并不存在,因此会自动创建,然后onupgradeneeded事件触发。只有在 `onupgradeneeded` 回调函数中,我们才能创建数据库的对象存储。首先,db = event.target.result我们使用 `onupgradeneeded` 设置变量db来保存数据库。然后,我们创建一个名为 `<object store_name>` 的对象存储notes



dbReq.onsuccess = function(event) {
  db = event.target.result;
}


Enter fullscreen mode Exit fullscreen mode

这里,它会在操作完成onsuccess后触发onupgradeneeded,如果我们刷新页面并再次打开数据库,它也会触发。所以,我们也会运行db = event.target.result这个函数来获取数据库以便使用它。



dbReq.onerror = function(event) {
  alert('error opening database ' + event.target.errorCode);
}


Enter fullscreen mode Exit fullscreen mode

最后,如果任何 IndexedDB 请求出错,都会onerror触发相应的事件,您可以根据需要处理该错误。我们这里就做一个alert.

将一些数据存入数据库

我们已经有了数据库,但是没有数据就做不了什么。让我们编写一个函数来添加便签吧!



function addStickyNote(db, message) {
  // Start a database transaction and get the notes object store
  let tx = db.transaction(['notes'], 'readwrite');
  let store = tx.objectStore('notes');

  // Put the sticky note into the object store
  let note = {text: message, timestamp: Date.now()};
  store.add(note);

  // Wait for the database transaction to complete
  tx.oncomplete = function() { console.log('stored note!') }
  tx.onerror = function(event) {
    alert('error storing note ' + event.target.errorCode);
  }
}


Enter fullscreen mode Exit fullscreen mode

为了实际演示这一点,让我们在代码中添加三个对我们的函数的调用,dbReq.onsuccess以便在数据库准备就绪后运行它们:



dbReq.onsuccess = function(event) {
  db = event.target.result;

  // Add some sticky notes
  addStickyNote(db, 'Sloths are awesome!');
  addStickyNote(db, 'Order more hibiscus tea');
  addStickyNote(db, 'And Green Sheen shampoo, the best for sloth fur algae grooming!');
}


Enter fullscreen mode Exit fullscreen mode

现在刷新浏览器中的 index.html,再次在开发者工具中选择“应用程序”>“IndexedDB”,点击对象存储,让我们看看我们的数据!

这是我们的 IndexedDB 面板的屏幕截图,其中存储了我们的对象。

现在我们已经存储了一些数据!正如你所看到的,笔记对象存储中的便签是以 JavaScript 对象的形式存储的。那么,那段代码到底在做什么呢?



let tx = db.transaction(['notes'], 'readwrite');
let store = tx.objectStore('notes');


Enter fullscreen mode Exit fullscreen mode

首先,我们在数据库上启动一个事务notes,将数据写入对象存储,然后我们从该事务中检索该对象存储。



let note = {text: message, timestamp: Date.now()};
store.add(note);


Enter fullscreen mode Exit fullscreen mode

我们将便笺表示为一个 JavaScript 对象,并通过调用该函数将其存储在对象存储中store.add



tx.oncomplete = function() { console.log('stored note!') }
tx.onerror = function(event) {
  alert('error storing note ' + event.target.errorCode);
}


Enter fullscreen mode Exit fullscreen mode

最后,就像我们打开数据库请求一样,此事务也有事件监听器;我们监听存储笔记的操作,无论是完成还是出错,都会触发事务oncomplete监听onerror器。

关于我们的便签,还有一点值得注意:每张便签都有一个Key递增的数字。所以,如果你在三张便签之后再放一张,它的键值就是 4。这些数字是怎么来的呢?在 IndexedDB 中,对象存储中的所有对象都有一个标识它们的,当我们使用以下代码创建对象存储时:

let notes = db.createObjectStore('notes', {autoIncrement: true});

autoIncrement选项表示我们希望存储中的每个对象都有一个递增的键。如果通过唯一名称存储和检索对象更有意义,您也可以创建使用字符串键的对象存储(例如,UUID 可以作为对象存储的字符串键,或者如果您有一个树懒对象存储,则可以使用树懒吱吱叫声的字符串编码作为键来标识每只树懒)。

现在让我们把这个addStickyNote功能添加到我们的实际 Web 应用中,以便用户可以点击提交便签。我们需要一个文本框来提交便签,所以在 id 为 `<div>` 的 div 元素中app,添加以下标签:



<div id="textbox">
  <textarea id="newmessage"></textarea>
  <button onclick="submitNote()">Add note</button>
</div>


Enter fullscreen mode Exit fullscreen mode

并将以下函数添加到 db.js 中,该函数会在每次用户提交笔记时运行:



function submitNote() {
  let message = document.getElementById('newmessage');
  addStickyNote(db, message.value);
  message.value = '';
}


Enter fullscreen mode Exit fullscreen mode

现在去掉对addStickyNotein的调用dbReq.onsuccess,然后如果我们进入 index.html 并在文本区域中输入一些内容,点击提交后,我们就会看到笔记被存储在 IndexedDB 中!

不过,在我们继续讲解如何检索数据以便显示之前,让我们先来谈谈使用 IndexedDB 的一个核心概念——事务!

在 IndexedDB 中,事务至关重要。

正如您在上一个示例中看到的,要访问notes对象存储,我们需要db.transaction创建一个事务,事务是一组对数据库的一个或多个请求。IndexedDB 中的所有操作都通过事务完成。因此,存储便签、打开数据库以及检索便签都是在事务内部执行的请求。

您也可以在同一事务中发出多个请求,例如,如果您在同一个对象存储中存储多个项目,则可以在同一事务中发出所有 store.add 请求,如下所示:



function addManyNotes(db, messages) {
  let tx = db.transaction(['notes'], 'readwrite');
  let store = tx.objectStore('notes');

  for (let i = 0; i < messages.length; i++) {
    // All of the requests made from store.add are part of
    // the same transaction
    store.add({text: messages[i], timestamp: Date.now()});
  }

  // When all of these requests complete, the transaction's oncomplete
  // event fires
  tx.oncomplete = function() {console.log('transaction complete')};
}


Enter fullscreen mode Exit fullscreen mode

就像请求有 `requests`onsuccessonerror`transactions` 事件处理程序一样,事务也有 `transactions` oncomplete、 `transaction-error`onerror和 ` onaborttransaction-rollback` 事件处理程序,我们可以分别使用这些处理程序来响应事务的完成、出错或回滚。

但是,将每个请求都放在事务中究竟有什么好处呢?请记住,IndexedDB 是一个异步 API,因此可能同时存在多个请求。假设我们在便笺存储中有一张便笺,上面写着“树懒真棒”,我们发出一个请求将便笺内容全部大写,然后又发出另一个请求在便笺上添加一个感叹号。如果没有事务,我们可能会遇到这样的情况:

两个数据库操作同时对同一张便签执行,且未启用事务;由于没有事务,这两个操作会在便签更新之前从对象存储中读取该便签,然后执行各自的更新操作,导致彼此覆盖。

我们启动了这makeAllCaps两个addExclamation操作,它们都检索到了未修改的“树懒真棒”笔记。addExclamation第一个操作先保存了带有感叹号的笔记。第二个操作makeAllCaps耗时更长,保存的笔记是“树懒真棒”,没有感叹号。这次makeAllCaps更新完全覆盖了之前的更新addExclamation

但是,有了事务,我们就实现了并发控制在对象存储中,一次只能有一个事务创建、修改或删除项,因此 IndexedDB 中实际发生的情况更像是这样:

两个数据库操作正在对同一张便笺执行,且都使用了事务;由于使用了事务,addExclamation 事务只有在 makeAllCaps 事务完成后才会开始。因此,当 addExclamation 事务读取便笺时,便笺上已经包含了 makeAllCaps 事务所做的修改。

事务makeAllCaps先启动,但由于addExclamation它与 makeAllCaps 使用同一个对象存储,所以要等到 makeAllCaps 完成后才会启动。因此,makeAllCaps 完成后,addExclamation 读取全大写的注释,然后两个编辑操作才会生效!🎉

这也意味着,如果一条道路是一个对象存储库,而街道清扫车和划线车在没有交易的情况下运行,那么划线车可能会在街道清扫车移动树枝之前完成划线,结果就会出现这种情况:

道路两侧,一条断枝周围画着一条界线,界线上用大写字母写着“失败”二字。

但是有了 IndexedDB 运行事务,扫街车可以清扫道路上的树枝,划线工可以画线,这样树懒就可以安全地骑自行车了!

树懒抱着一辆自行车

在继续之前,还有一点需要了解,那就是对同一对象存储进行的事务,只有在添加、修改或删除数据时才会一次执行一个;换句话说,它们是readwrite事务,其创建方式如下:

let tx = db.transaction(['notes', 'someOtherStore'], 'readwrite');

这里我们创建一个读写事务,并声明它会影响对象存储notesA 和 B。someOtherStore由于它是读写事务,因此只有在任何其他涉及这两个对象存储的事务完成后,它才能开始执行。

虽然读写事务一次只能执行一个,但还有readonly事务;您可以同时创建任意多个事务来访问同一个对象存储,因为我们不需要阻止它们互相干扰数据!您可以像这样创建它们:



// These transactions can all do their thing at the same time, even with
// overlapping object stores!
let tx = db.transaction(['notes', 'someOtherStore'], 'readonly');
let tx2 = db.transaction(['notes'], 'readonly');
let tx3 = db.transaction(['someOtherStore'], 'readonly');


Enter fullscreen mode Exit fullscreen mode

取出一张便签

现在我们了解了事务和只读事务的工作原理,接下来让我们从便笺库中检索便笺以便显示它们。如果我们只从数据库中获取一个项目,我们会使用对象存储的get方法,如下所示:



// Set up an object store and transaction
let tx = db.transaction(['notes'], 'readonly');
let store = tx.objectStore('notes');

// Set up a request to get the sticky note with the key 1
let req = store.get(1);

// We can use the note if the request succeeds, getting it in the
// onsuccess handler
req.onsuccess = function(event) {
  let note = event.target.result;

  if (note) {
    console.log(note);
  } else {
    console.log("note 1 not found")
  }
}

// If we get an error, like that the note wasn't in the object
// store, we handle the error in the onerror handler
req.onerror = function(event) {
  alert('error getting note 1 ' + event.target.errorCode);
}


Enter fullscreen mode Exit fullscreen mode

我们发起一笔交易,请求笔记存储中键值为 1 的笔记以获取请求结果,然后我们会在请求的onsuccess处理程序中使用检索到的笔记,或者如果收到错误,则在处理程序中处理该错误onerror。请注意,如果便笺不存在,onsuccess仍然会触发,但event.target.result结果会是undefined

这个模式和我们之前用于打开数据库的处理程序类似;我们发起请求,然后在onsuccess处理程序中获取结果或处理错误onerror。但我们想要的不仅仅是一条笔记,而是所有笔记。所以我们需要获取所有笔记,为此我们使用游标

使用光标检索数据并显示您的便笺

从对象存储中检索所有项的语法很奇怪:



function getAndDisplayNotes(db) {
  let tx = db.transaction(['notes'], 'readonly');
  let store = tx.objectStore('notes');

  // Create a cursor request to get all items in the store, which 
  // we collect in the allNotes array
  let req = store.openCursor();
  let allNotes = [];

  req.onsuccess = function(event) {
    // The result of req.onsuccess in openCursor requests is an
    // IDBCursor
    let cursor = event.target.result;

    if (cursor != null) {
      // If the cursor isn't null, we got an item. Add it to the
      // the note array and have the cursor continue!
      allNotes.push(cursor.value);
      cursor.continue();
    } else {
      // If we have a null cursor, it means we've gotten
      // all the items in the store, so display the notes we got.
      displayNotes(allNotes);
    }
  }

  req.onerror = function(event) {
    alert('error in cursor request ' + event.target.errorCode);
  }
}


Enter fullscreen mode Exit fullscreen mode

执行该函数后,以下是所有步骤:



let tx = db.transaction(['notes'], 'readonly');
let store = tx.objectStore('notes');


Enter fullscreen mode Exit fullscreen mode

在函数开始时,我们在notes对象存储上创建一个只读事务。然后我们获取存储,并通过该store.openCursor()方法获取请求。这意味着我们再次使用请求结果及其onsuccess处理onerror程序来处理这些结果。

在 onsuccess 处理程序中,事件的结果是一个IDBCursor,其中包含key光标所持便笺的 ID,以及便笺本身作为光标的 ID value



let cursor = event.target.result;
if (cursor != null) {
  allNotes.push(cursor.value);
  cursor.continue();
} else {


Enter fullscreen mode Exit fullscreen mode

在 if 语句中,如果游标不为空,则表示我们还有另一张便签,因此我们将游标添加value到我们的便签数组中,并通过调用继续检索便签cursor.continue



} else {
  displayNotes(allNotes);
}


Enter fullscreen mode Exit fullscreen mode

但如果光标为空,则没有更多笔记可供检索,因此我们将笔记传递给displayNotes函数来显示笔记。

嗯,这cursor.continue()感觉有点像 while 循环,但实际上并没有循环或控制流。那么,我们究竟是如何实现循环的呢?这行代码会给你一些提示:

req.onsuccess = function(event) {

原来,每次调用 `onSuccess` 时cursor.continue(),都会触发一个事件,并将包含下一个项目的游标发送到 `onSuccess` 处理程序。因此onsuccess,在每次调用 `onSuccess` 时,我们都会收集一个新的便签,直到遇到游标为空的 `onSuccess` 事件。这就是我们使用游标遍历数据的方式。

现在,为了显示这些便签,在 index.html 文件中,在文本框 div 之后,在文本框下方添加一个 div 来存储我们的便签:

<div id="notes"></div>

在 db.js 文件中添加以下函数以显示备注:



function displayNotes(notes) {
  let listHTML = '<ul>';
  for (let i = 0; i < notes.length; i++) {
    let note = notes[i];
    listHTML += '<li>' + note.text + ' ' + 
      new Date(note.timestamp).toString() + '</li>';
  }

  document.getElementById('notes').innerHTML = listHTML;
}


Enter fullscreen mode Exit fullscreen mode

该函数只是将每个笔记转换为一个<li>标签,并使用传统的 JavaScript 将它们显示为列表。

现在我们有了显示所有便签的函数,让我们把它添加到几个地方。我们希望在首次打开应用时能够看到所有便签,因此在数据库首次打开时,我们应该调用以下getAndDisplayNotes函数dbReq.onsuccess



dbReq.onsuccess = function(event) {
  db = event.target.result;
  // Once the database is ready, display the notes we already have!
  getAndDisplayNotes(db);
}


Enter fullscreen mode Exit fullscreen mode

添加便笺后,应该能够立即看到它,因此addStickyNote,让我们将事务完成回调更改为调用getAndDisplayNotes



tx.oncomplete = function() { getAndDisplayNotes(db); }


Enter fullscreen mode Exit fullscreen mode

现在用 Chrome 浏览器重新打开页面,尝试添加更多注释。它应该看起来像这样!

该网页应用程序显示了列表标签中的笔记,以及包含消息的文本区域。

最后,我们来创建一个模式,优先显示最新的笔记,看看为什么它被称为 IndexedDB!

索引,将索引放入 IndexedDB 中

我们有这样一个便签存储系统,并且存储的便签带有时间戳,所以应该能够检索某个时间范围内的所有便签(例如过去 10 分钟内的所有便签),或者能够先检索最新的便签,对吧?

当然可以,但要通过时间戳字段进行查询,我们需要在 notes 对象存储中为该字段创建一个索引。有了索引之后,我们就可以通过它进行查询了。但请记住,对数据库结构的任何更改都需要在数据库请求onupgradeneeded处理程序中进行,因此我们需要更新数据库版本才能创建索引,如下所示:



// We update the version of the database to 2 to trigger
// onupgradeneeded
let dbReq = indexedDB.open('myDatabase', 2);
dbReq.onupgradeneeded = function(event) {
  db = event.target.result;

  // Create the notes object store, or retrieve that store if it
  // already exists.
  let notes;
  if (!db.objectStoreNames.contains('notes')) {
    notes = db.createObjectStore('notes', {autoIncrement: true});
  } else {
    notes = dbReq.transaction.objectStore('notes');
  }

  // If there isn't already a timestamp index in our notes object
  // store, make one so we can query notes by their timestamps
  if (!notes.indexNames.contains('timestamp')) {
    notes.createIndex('timestamp', 'timestamp');
  }
}


Enter fullscreen mode Exit fullscreen mode

首先,我们将数据库版本更新为 2,这表明数据库结构正在发生变化,因此会onupgradeneeded触发该事件。

现在我们进行版本升级,而笔记对象存储之前已经存在,所以我们检查是否已经存在笔记存储db.objectStoreNames

if (!db.objectStoreNames.contains('notes')) {

如果该对象存储已存在,我们可以使用以下方式检索它dbReq.transaction.objectStore

notes = dbReq.transaction.objectStore('notes');

最后,我们添加一个索引createIndex

notes.createIndex('timestamp', 'timestamp');

第一个参数是索引的名称,第二个参数是索引的keyPath。索引本身实际上就是一个对象存储,因此索引中的每个项都有一个键。所以,如果您为索引指定 keyPath timestamp,那么对象存储中每个对象的时间戳将作为该索引的键。

此外,还有一个可选的第三个选项对象参数。假设我们的笔记有标题,并且我们希望规定如果一条笔记的标题与其他笔记相同,则不能将其存储。我们可以通过创建一个唯一的标题索引来实现这一点,如下所示:

notes.createIndex('title', 'title', {unique: true});

要查看新的索引,更新后onupgradeneeded,请在 Chrome 中刷新 index.html(可能需要关闭 Chrome 窗口才能看到更改),然后再次转到“开发者工具”>“应用程序”>“IndexedDB”,您应该能够在笔记对象存储中看到新的时间戳索引:

这是开发者工具中 IndexedDB 面板的屏幕截图,显示的是 IndexedDB 中的一个索引。我们便签的时间戳用作索引键。IndexedDB 中的索引会列在面板中,位于其对象存储下方。

如您所见,笔记现在以时间戳作为主键进行排列。实际上,作为一种对象存储,索引拥有getopenCursor常规对象存储相同的方法。例如,我们可以通过调用以下方法请求列表中的第一条笔记:

tx.objectStore('notes').index('timestamp').get(1533144673015);

好了。现在我们有了很棒的新索引,让我们为 Web 应用添加一个模式,用来切换笔记的显示顺序。首先,在 db.js 文件中添加一个全局布尔变量:



let reverseOrder = false;


Enter fullscreen mode Exit fullscreen mode

然后,在 getAndDisplayNotes 中,我们只需要更新我们的请求,以便我们使用时间戳索引,并选择从哪个方向读取便笺。



let tx = db.transaction(['notes'], 'readonly');
let store = tx.objectStore('notes');

// Retrieve the sticky notes index to run our cursor query on; 
// the results will be ordered by their timestamp
let index = store.index('timestamp');

// Create our openCursor request, on the index rather than the main
// notes object store. If we're going in reverse, then specify the
// direction as "prev". Otherwise, we specify it as "next".
let req = index.openCursor(null, reverseOrder ? 'prev' : 'next');


Enter fullscreen mode Exit fullscreen mode

在`getIndexPath` 方法中store.index(),我们检索指定名称的索引,就像从事务中检索对象存储一样。现在,我们可以针对该索引定义一个游标请求,以按时间戳排序获取笔记。

index.openCursor它有两个可选参数。第一个参数(如果不为空)允许我们指定要检索的项目范围。例如,如果我们只想获取过去一小时内的便签,可以这样打开光标:



let anHourAgoInMilliseconds = Date.now() - 60 * 60 * 1000;

// IDBKeyRange is a global variable for defining ranges to query
// indices on
let keyRange = IDBKeyRange.lowerBound(anHourAgoInMilliseconds);
let req = index.openCursor(keyRange, 'next');


Enter fullscreen mode Exit fullscreen mode

第二个参数是我们想要检索项目的顺序,可以是“是”'prev'或“否'next'”,因此我们通过传入“是”来指定方向reverseOrder ? 'prev' : 'next'

最后,让我们看看实际效果;在 index.html 文件中,添加另一个函数。这个函数用于翻转我们显示的笔记顺序:



function flipNoteOrder(notes) {
  reverseOrder = !reverseOrder;
  getAndDisplayNotes(db);
}


Enter fullscreen mode Exit fullscreen mode

要从用户界面使用 flipNoteOrder 函数,请在 index.html 中添加一个用于翻转音符顺序的按钮。



<button onclick="flipNoteOrder()">Flip note order</button>


Enter fullscreen mode Exit fullscreen mode

刷新 Chrome 浏览器后,翻转按钮应该就能正常工作了!

我们的网页应用演示了翻页顺序按钮现在可以正常工作。文本区域包含以下消息。

太棒了!现在我们可以更改笔记的显示顺序了!现在你已经了解了 IndexedDB 的基础知识。还有一些我们没有实际演示的功能,例如删除对象、在 IndexedDB 中存储二进制数据以及多字段索引,但这应该足以让你以身作则,开始使用 IndexedDB 构建 Web 应用程序。

正如你所见,IndexedDB 的基本 API 虽然功能强大,但使用起来并不方便。我不知道你怎么想,但对我来说,这些事件监听器用起来很不方便,而且我第一次研究如何为 IndexedDB 代码编写测试覆盖率时,也花了不少心思去琢磨这些处理程序。此外,我们又该如何为这个 API 编写自动化测试呢?

接下来的几期教程中,我会教大家如何重构这段代码使其易于测试;再下一期教程,我会教大家如何重构它使其更易于使用!下次见!

一只三趾树懒爬上横杆,面带微笑

保持懒惰!

本教程第二部分:IndexedDB 的测试覆盖率

【本教程第三部分正在编写中】

文章来源:https://dev.to/andyhaskell/build-a-basic-web-app-with-indexeddb-38ef