JavaScript IndexedDB 完整指南

PHP定製開發發表於2022-09-30

本文將透過一個crmeb小教程向你介紹  IndexedDB,並將  IndexedDB 與其他可用選項進行比較。 IndexedDB 用於在瀏覽器中儲存資料,對於需要離線工作的 web 應用程式(如大多數進步的 web 應用程式)尤其重要。

首先,讓我們介紹一下為什麼需要將資料儲存在 web 瀏覽器中。資料在 web 應用程式中無處不在 —— 使用者互動建立資料、查詢資料、更新資料和刪除資料。如果沒有儲存這些資料的方法,就不可能允許使用者互動跨多個 web 應用程式的使用保持狀態。你通常會使用 MySQL、Postgres、MongoDB、Neo4j、ArangoDB 等資料庫來處理這些儲存,但如果你希望應用程式離線工作呢?

這在不斷髮展的 web 應用程式中尤為重要,這些應用程式複製了原生應用程式的感覺,但卻位於瀏覽器中。這些漸進的 web 應用程式必須離線工作,因此需要一個儲存選項。幸運的是,有幾種關於如何在瀏覽器中儲存資料的工具,可以線上和離線訪問資料。

1. 瀏覽器儲存方式

關於如何在瀏覽器中儲存資料,Web 標準提供了三個主要 API:

  • Cookies:此資料儲存在瀏覽器中, Cookies 的大小限制為  4k。通常當伺服器響應一個請求時,它們可能包含一個  SET-COOKIE 頭,給瀏覽器一個要儲存的鍵和值。然後,客戶端應該在未來的請求頭中包含這個  cookie,這將允許伺服器識別瀏覽器會話等。這些  cookie 通常具有  HTTP-Only 屬性,這意味著不能透過客戶端指令碼訪問  cookie。這使得  cookie 不是儲存離線資料的好選擇。
  • LocalStorage/SessionStorageLocalStorage / SessionStorage 是瀏覽器內建的鍵值儲存,其中每個鍵的大小限制為  5MBLocalStorage 儲存資料,直到刪除為止,而  sessionStorage 將在瀏覽器關閉時清除自己。除此之外,它們的 API 是相同的。可以使用  window.localStorage.setItem("Key", "Value") 新增鍵值對。並使用  window.localStorage.getItem("Key") 檢索一個值。注意, LocalStorage API 是同步的,因此使用它會阻塞瀏覽器中的其他活動,這可能是一個問題。
  • IndexedDB:一個內建在瀏覽器中的完整文件資料庫,沒有儲存限制,它允許你非同步訪問資料,這對於防止複雜操作阻塞呈現和其他活動非常有效。這就是我們將在下面深入討論的內容。

在這些方式中, localStorage 是進行簡單操作和儲存少量資料的好選擇。對於更復雜或常規的操作, IndexedDB 可能是更好的選擇,特別是在需要非同步獲取資料的情況下。

IndexedDB API 比 LocalStorage API 更復雜。所以,讓我們用  IndexedDB 構建一些東西,讓你更好地感受它是如何工作的!

2. 使用案例

建立一個新的 HTML 檔案,我們稱之為  index.html,內容如下:

<!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 Todo List</title>
    <style>
        body {            text-align: center;
        }        
        h1 {            color: brown;
        }    </style></head><body>
    <main>
        <h1>IndexedDB Todo-List</h1>
        <div id="form">
            <input type="text" placeholder="new todo here">
            <button>Add Todo</button>
        </div>
        <div id="todos">
            <ul></ul>
        </div>
    </main>
    
    
    <script>
        // 儲存輸入的變數
        const textInput = document.querySelector("[type='text']")        const button = document.querySelector("button")        // 儲存 todos 的陣列
        const todos = []        // 渲染 todos 的函式
        function renderTodos(){            const ul = document.querySelector("#todos ul")
            ul.innerHTML = ""
            for (todo of todos){
                ul.innerHTML += `<li>${todo}</li>`
            }
        }        renderTodos()    </script></body></html>複製程式碼

現在我們可以開始設定  IndexedDB 了。在瀏覽器中開啟此檔案。如果你正在使用 VS Code,可以用像   這樣的擴充套件。

IndexedDB 支援非常好,但我們仍然想檢查瀏覽器是否支援 API 的實現,以便你可以新增以下函式來檢查。

 // 檢查 indexedDB 實現並返回它的函式function getIndexDB() {  const indexedDB =    window.indexedDB ||    window.mozIndexedDB ||    window.webkitIndexedDB ||    window.msIndexedDB ||    window.shimIndexedDB;    if (indexedDB){        return indexedDB
    }    console.error("indexedDB not supported by this browser")    return null}複製程式碼

這個函式要麼返回  IndexedDB 的瀏覽器實現,要麼返回瀏覽器不支援的日誌。你可以記錄在瀏覽器中呼叫  getIndexDB 的結果,以確認瀏覽器支援  IndexedDB

下面你可以看到相容性列表。你可以在這裡找到完整的列表,包括移動瀏覽器。

JavaScript IndexedDB 完整指南

現在讓我們用  indexedDB.open("database name", 1) 開啟一個資料庫。 open 的第一個引數是資料庫的名稱,第二個引數是資料庫的版本。如果你希望觸發一個  onupgraderequired,你應該在  .open 呼叫中增加版本號。 open 方法將返回一個具有多個屬性的物件,包括  onerroronupgradenneeded 和  onsuccess,每個屬性都接受一個回撥函式,在相關事件發生時執行。

const indexedDB = getIndexDB()// console.log(indexedDB)const request = indexedDB.open("todoDB", 1)console.log(request)renderTodos();複製程式碼

你應該看到一個  console.log,其中顯示一個  IDBOpenDBRequest 物件。 IndexedDB 是基於事件的,這符合它的非同步模型。接下來,讓我們看看資料庫啟動時可能發生的事件。首先,我們將監聽  request.onerror 事件,以防訪問資料庫時出現任何錯誤。

const indexedDB = getIndexDB()// console.log(indexedDB)const request = indexedDB.open("todoDB", 1)//console.log(request)// onerror 處理request.onerror = (event) => console.error("IndexDB Error: ", event)renderTodos();複製程式碼

我們將監聽的下一個事件是  request.onupgradeneeded 事件,當試圖開啟一個版本號高於資料庫當前版本號的資料庫時,該事件就會執行。這是建立儲存 / 表及其模式的函式。這個函式在每個版本號下只執行一次。因此,如果你決定更改  onupgradedened 回撥來更新你的模式或建立新的儲存,那麼版本號也應該在下一個  .open 呼叫中增加。儲存本質上相當於傳統資料庫中的表。

const indexedDB = getIndexDB();// console.log(indexedDB)const request = indexedDB.open("todoDB", 1);//console.log(request)//onerror handlingrequest.onerror = (event) => console.error("IndexDB Error: ", event);//onupgradeneededrequest.onupgradeneeded = () => {  // 獲取資料庫連線
  const db = request.result;  // 定義一個新儲存
  const store = db.createObjectStore("todos", {    keyPath: "id",    autoIncrement: true,
  });  // 指定一個屬性作為索引
  store.createIndex("todos_text", ["text"], {unique: false})
};renderTodos();複製程式碼

在  onupgradeneeded 中,我們做了以下幾點:

  • 獲取資料庫物件(如果  onupgradenneeded 函式正在執行,你就知道它是可用的)
  • 建立一個名為  todos 的新儲存 / 表 / 集合,其鍵  id 是一個自動遞增的數字(記錄的唯一識別符號)
  • 指定  todos_text 作為索引,這允許我們稍後透過  todos_text 搜尋資料庫。如果不打算按特定屬性進行搜尋,則不必建立索引。

最後要處理  request.onsuccess 事件,該事件在資料庫連線和儲存全部設定和配置之後執行。你可以利用這個機會提取  todo 列表並將它們注入到我們的陣列中。

//onsuccessrequest.onsuccess = () => {    console.log("Database Connection Established")    // 獲取資料庫連線
    const db = request.result
    // 建立事務物件
    const tx = db.transaction("todos", "readwrite")    // 建立一個與我們儲存的事務
    const todosStore = tx.objectStore("todos")    // 得到所有待辦事項
    const query = todosStore.getAll()    // 使用資料查詢
    query.onsuccess =  () => {        console.log("All Todos: ", query.result)        for (todo of query.result){
            todos.push(todo.text)
        }        renderTodos()
    }
}複製程式碼

在  onsuccess 中,我們做了以下幾點:

  • 獲取資料庫連線
  • 建立事務
  • 指定我們在哪個儲存上進行事務處理
  • 執行一個  getAll 查詢來獲取儲存中的所有文件 / 記錄
  • 在查詢特定的  onsuccess 事件中,我們迴圈遍歷  todos,將它們存入  todos 陣列並呼叫  renderTodos(),因此它們被渲染到 dom 中

你應該在控制檯中看到一個  console.log,其中包含一個空陣列。

** 錯誤提示:** 如果你正在執行一個熱重新載入 web 伺服器,如 liveserver,你可能會看到一個錯誤,沒有儲存。這是因為  onupgradedneeded 函式在你寫完函式之前就執行了。因此,它不會為該版本號再次執行。解決方案是增加表的版本號,這將建立一個  onupgradenneeded,並且  onupgradenneeded 回撥將在下次頁面重新整理時執行。

現在我們已經有了資料庫設定,可以對我們希望發生的任何其他事件遵循相同的模式。例如,讓我們在單擊按鈕時建立一個事件,該事件不僅會向 dom 新增一個新的  todo,還會向資料庫新增一個新的  todo,以便在頁面重新整理時顯示。

// button 事件button.addEventListener("click", (event) => {    // 設定一個事務
    const db = request.result
    const tx = db.transaction("todos", "readwrite")    const todosStore = tx.objectStore("todos")    // 增加一個 todo
    const text = textInput.value
    todos.push(text) // 增加一個 todo 到陣列
    todosStore.put({text}) // 新增到 indexedDB
    renderTodos() // 更新 dom})複製程式碼

現在你可以新增  todos,因為你使用的是  IndexedDB,無論你是線上還是離線,它都可以工作。

新增一些  todo,當你重新整理頁面時,你將看到  todo 持續存在。它們也會顯示在查詢結果的  console.log 中,每個  todo 都有一個唯一的 ID。到目前為止,完整的程式碼應該如下所示:

<!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 Todo List</title>
    <style>
      body {        text-align: center;
      }      h1 {        color: brown;
      }    </style>
  </head>
  <body>
    <main>
      <h1>IndexedDB Todo-List</h1>
      <div id="form">
        <input type="text" placeholder="new todo here" />
        <button>Add Todo</button>
      </div>
      <div id="todos">
        <ul></ul>
      </div>
    </main>
    <script>
      // 儲存輸入的變數
      const textInput = document.querySelector("[type='text']");      const button = document.querySelector("button");      // 儲存 todos 的陣列
      const todos = [];      // 渲染 todos 的函式
      function renderTodos() {        const ul = document.querySelector("#todos ul");
        ul.innerHTML = "";        for (todo of todos) {
          ul.innerHTML += `<li>${todo}</li>`;
        }
      }      // 檢查 indexedDB 實現並返回它的函式
      function getIndexDB() {        const indexedDB =          window.indexedDB ||          window.mozIndexedDB ||          window.webkitIndexedDB ||          window.msIndexedDB ||          window.shimIndexedDB;        if (indexedDB) {          return indexedDB;
        }        console.log("indexedDB not supported by this browser");        return null;
      }      const indexedDB = getIndexDB();      // console.log(indexedDB)
      const request = indexedDB.open("todoDB", 2);      // console.log(request)
      // onerror 處理
      request.onerror = (event) => console.error("IndexDB Error: ", event);      // onupgradeneeded
      request.onupgradeneeded = () => {        // 獲取資料庫連線
        const db = request.result;        // 定義一個新儲存
        const store = db.createObjectStore("todos", {          keyPath: "id",          autoIncrement: true,
        });        // 指定一個屬性作為索引
        store.createIndex("todos_text", ["text"], {unique: false})
      };      // onsuccess
      request.onsuccess = () => {          console.log("Database Connection Established")          // 獲取資料庫連線
          const db = request.result
          // 建立事務物件
          const tx = db.transaction("todos", "readwrite")          // 建立一個我們的儲存事務
          const todosStore = tx.objectStore("todos")          // 獲取所有 todo
          const query = todosStore.getAll()          // 使用資料查詢
          query.onsuccess =  () => {              console.log("All Todos: ", query.result)              for (todo of query.result){
                  todos.push(todo.text)
              }              renderTodos()
          }
      }      // button 事件
      button.addEventListener("click", (event) => {          // 設定一個事務
          const db = request.result
          const tx = db.transaction("todos", "readwrite")          const todosStore = tx.objectStore("todos")          // 新增一個 todo
          const text = textInput.value
          todos.push(text) // 新增 todo 到陣列
          todosStore.put({text}) // 新增到 indexedDB
          renderTodos() // 更新 dom
      })      renderTodos();    </script>
    
  </body></html>複製程式碼

todosStore 物件上可用於不同型別事務的其他方法:

  • clear: 刪除  store 中的所有記錄
  • add:用給定的  id 插入一個記錄(如果它已經存在就會出錯)
  • put:用給定的  id 插入或更新一個記錄(如果已經存在就會更新)
  • get:用特定的  id 獲取記錄
  • getAll:從  store 中獲取所有記錄
  • count:返回  store 中的記錄數
  • createIndex:基於給定的  index 建立物件來查詢
  • delete: 對給定  id 進行刪除記錄

3. 效能和其他考慮因素

你需要考慮以下幾點:

  • 並不是所有瀏覽器都支援將檔案儲存為  blob,你會發現更好的方式:將它們儲存為  arraybuffer
  • 有些瀏覽器可能不支援在私人瀏覽模式下寫入  IndexedDB
  • IndexedDB 在寫入物件時會建立結構化克隆,這會阻塞主執行緒,所以如果你的大物件中填充了更多巢狀的物件,這可能會導致一些延遲。
  • 如果使用者關閉瀏覽器,則任何未完成的事務都有可能被中止。
  • 如果另一個瀏覽器選項卡開啟了一個更新的資料庫版本號的應用程式,它將被阻止升級,直到所有舊版本選項卡關閉 / 重新載入。幸運的是,你可以使用  onblocked 事件來觸發警報,通知使用者他們需要這樣做。

你可以在  MDN 文件中找到更多  IndexedDB 的限制。

雖然  indexedDB 非常適合讓你的應用程式離線工作,但它不應該成為你的主資料儲存。在網際網路連線中,你可能希望將  indexedDB 與外部資料庫同步,以便在使用者清除瀏覽器資料時不會丟失使用者的資訊。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70021881/viewspace-2916986/,如需轉載,請註明出處,否則將追究法律責任。

相關文章