JavaScript och IndexedDB
I den här artikeln kommer vi att förklara JavaScript och IndexedDB.
Denna handledning ger en steg-för-steg-förklaring av JavaScript och IndexedDB
, inklusive praktisk exempelkod i varje steg för att hjälpa dig fördjupa din förståelse.
YouTube Video
javascript-indexed-db.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <title>JavaScript & HTML</title>
6 <style>
7 * {
8 box-sizing: border-box;
9 }
10
11 body {
12 margin: 0;
13 padding: 1em;
14 padding-bottom: 10em;
15 font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16 background-color: #f7f9fc;
17 color: #333;
18 line-height: 1.6;
19 }
20
21 .container {
22 max-width: 800px;
23 margin: 0 auto;
24 padding: 1em;
25 background-color: #ffffff;
26 border: 1px solid #ccc;
27 border-radius: 10px;
28 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
29 }
30
31 .container-flex {
32 display: flex;
33 flex-wrap: wrap;
34 gap: 2em;
35 max-width: 1000px;
36 margin: 0 auto;
37 padding: 1em;
38 background-color: #ffffff;
39 border: 1px solid #ccc;
40 border-radius: 10px;
41 box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
42 }
43
44 .left-column, .right-column {
45 flex: 1 1 200px;
46 min-width: 200px;
47 }
48
49 h1, h2 {
50 font-size: 1.2rem;
51 color: #007bff;
52 margin-top: 0.5em;
53 margin-bottom: 0.5em;
54 border-left: 5px solid #007bff;
55 padding-left: 0.6em;
56 background-color: #e9f2ff;
57 }
58
59 button {
60 display: block;
61 margin: 1em auto;
62 padding: 0.75em 1.5em;
63 font-size: 1rem;
64 background-color: #007bff;
65 color: white;
66 border: none;
67 border-radius: 6px;
68 cursor: pointer;
69 transition: background-color 0.3s ease;
70 }
71
72 button:hover {
73 background-color: #0056b3;
74 }
75
76 #output {
77 margin-top: 1em;
78 background-color: #1e1e1e;
79 color: #0f0;
80 padding: 1em;
81 border-radius: 8px;
82 min-height: 200px;
83 font-family: Consolas, monospace;
84 font-size: 0.95rem;
85 overflow-y: auto;
86 white-space: pre-wrap;
87 }
88
89 .highlight {
90 outline: 3px solid #ffc107; /* yellow border */
91 background-color: #fff8e1; /* soft yellow background */
92 transition: background-color 0.3s ease, outline 0.3s ease;
93 }
94
95 .active {
96 background-color: #28a745; /* green background */
97 color: #fff;
98 box-shadow: 0 0 10px rgba(40, 167, 69, 0.5);
99 transition: background-color 0.3s ease, box-shadow 0.3s ease;
100 }
101 </style>
102</head>
103<body>
104 <div class="container">
105 <h1>JavaScript Console</h1>
106 <button id="executeBtn">Execute</button>
107 <div id="output"></div>
108 </div>
109
110 <script>
111 // Override console.log to display messages in the #output element
112 (function () {
113 // Override console.log
114 const originalLog = console.log;
115 console.log = function (...args) {
116 originalLog.apply(console, args);
117 const message = document.createElement('div');
118 message.textContent = args
119 .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
120 .join(" ");
121 output.appendChild(message);
122 };
123
124 // Override console.error
125 const originalError = console.error;
126 console.error = function (...args) {
127 originalError.apply(console, args);
128 const message = document.createElement('div');
129 message.textContent = args
130 .map(arg => (typeof arg === "object" && arg !== null ? JSON.stringify(arg) : String(arg)))
131 .join(" ");
132 message.style.color = 'red'; // Color error messages red
133 output.appendChild(message);
134 };
135 })();
136
137 document.getElementById('executeBtn').addEventListener('click', () => {
138 // Prevent multiple loads
139 if (document.getElementById('externalScript')) return;
140
141 const script = document.createElement('script');
142 script.src = 'javascript-indexed-db.js';
143 script.id = 'externalScript';
144 //script.onload = () => console.log('javascript-indexed-db.js loaded and executed.');
145 //script.onerror = () => console.log('Failed to load javascript-indexed-db.js.');
146 document.body.appendChild(script);
147 });
148 </script>
149</body>
150</html>
JavaScript och IndexedDB
IndexedDB
är en asynkron nyckel-värde databashanterare inbyggd i webbläsare. Den erbjuder funktioner liknande relationsdatabaser och låter dig lagra och söka stora mängder strukturerad data på klientsidan. Det är särskilt användbart för offline-kapabla applikationer och PWA:er (Progressive Web Apps).
Funktioner i IndexedDB
- Arbetar på ett asynkront och händelsedrivet sätt.
- JavaScript-objekt kan lagras i object stores (objektbutiker).
- Det är möjligt att söka med frågor eller index.
- Den har stor lagringskapacitet (hundratals MB eller mer), vilket gör att du kan lagra mycket mer data än med cookies eller localStorage.
Öppna och skapa en databas
För att använda IndexedDB
måste du först öppna en databas. Om den inte finns skapas den automatiskt.
1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
open
-metoden öppnar databasen asynkront och utlöser följande tre händelser.
onsuccess
1// Fired when database is successfully opened
2request.onsuccess = (event) => {
3 const db = event.target.result; // Database instance
4 console.log('Database opened successfully:', db.name);
5};
onsuccess
-händelsen utlöses när databasen öppnas framgångsrikt. Efterföljande operationer ska utföras med hjälp avrequest.result
, som nu är tillgänglig.
onerror
1// Fired when database fails to open
2request.onerror = (event) => {
3 console.error('Failed to open database:', event.target.error);
4};
onerror
-händelsen utlöses om databasen inte kan öppnas. Felloggning och felhantering bör utföras här.
onupgradeneeded
1// Fired when database is newly created or upgraded
2request.onupgradeneeded = (event) => {
3 const db = event.target.result;
4 console.log('Database upgrade needed (or newly created):', db.name);
5
6 // Example: Create an object store (like a table) if it doesn’t exist
7 if (!db.objectStoreNames.contains('users')) {
8 db.createObjectStore('users', { keyPath: 'id' });
9 console.log('Object store "users" created');
10 }
11};
onupgradeneeded
utlöses när databasen skapas på nytt eller när den angivna versionen är högre än den aktuella versionen. Att skapa tabeller (object stores) och definiera schemat ska göras vid denna tidpunkt.
Skapa en objektbutik (Object Store)
Skapa först en 'object store' (motsvarande en tabell) inom ramen för onupgradeneeded
.
1request.onupgradeneeded = function (event) {
2 const db = event.target.result;
3 console.log("onupgradeneeded triggered. Database version:", db.version);
4 if (!db.objectStoreNames.contains('users')) {
5 console.log("Creating object store: users");
6 const store = db.createObjectStore('users', { keyPath: 'id' });
7 store.createIndex('name', 'name', { unique: false });
8 }
9};
Här tillämpas följande inställningar:.
-
createObjectStore
createObjectStore
är en metod för att skapa en ny objektbutik i databasen. Du kan lagra poster i en objektbutik och definiera hur data hanteras genom att specificerakeyPath
och andra inställningar. Denna process måste göras inom ramen föronupgradeneeded
-händelsen. -
keyPath: 'id'
AngekeyPath
till egenskapenid
, vilket unikt identifierar varje post som en primärnyckel. Detta gör attid
automatiskt används vid tillägg, sökning eller uppdatering av data. -
createIndex
Använd metodencreateIndex
för att skapa ett index baserat på egenskapenname
för sökningar. Genom att sättaunique: false
tillåts flera poster med sammaname
.
Hantera framgångsrika databasanslutningar
Tilldela processer som ska köras vid en framgångsrik databasanslutning till onsuccess
-händelsen. I denna process erhåller du databasinstansen och förbereder för att läsa och skriva data.
1request.onsuccess = function (event) {
2 const db = event.target.result;
3 console.log('Database opened successfully');
4 // Use db for reading and writing in subsequent operations
5};
-
Denna process körs när anslutningen till
IndexedDB
-databasen är slutförd och lyckad. -
event.target.result
innehåller den öppnade databasinstansen (IDBDatabase
-objektet), vilket används för att starta transaktioner och komma åt objektbutiker. -
Faktiska läs- och skrivoperationer, såsom tillägg, hämtning, uppdatering och borttagning av data, utförs med
db
-objektet.
Vid denna punkt är databasen redo och du kan påbörja transaktioner på ett säkert sätt.
Lägga till data
Så här lägger du till ny data i IndexedDB
.
1function addUser(db, user) {
2 const transaction = db.transaction('users', 'readwrite');
3 const store = transaction.objectStore('users');
4 const request = store.add(user);
5
6 request.onsuccess = () => {
7 console.log('User added:', user);
8 };
9
10 request.onerror = () => {
11 console.error('Add failed:', request.error);
12 };
13}
14
15// Example: Add a user
16request.onsuccess = function (event) {
17 const db = event.target.result;
18 addUser(db, { id: 1, name: 'Alice' });
19 addUser(db, { id: 2, name: 'Bob' });
20 addUser(db, { id: 3, name: 'John' });
21};
- Skapa en transaktion med
db.transaction()
och ange den objektbutik som ska användas samt läge (i detta fallreadwrite
). - Lägg till ny data med hjälp av metoden
store.add()
. - Transaktioner slutförs automatiskt, men om du vill gruppera flera operationer kan du hantera dem med händelser för transaktionsslut.
Hämta data (sökning med primärnyckel)
Så här hämtar du specifik data med primärnyckel.
1function getUserById(db, id) {
2 const transaction = db.transaction('users', 'readonly');
3 const store = transaction.objectStore('users');
4 const request = store.get(id);
5
6 request.onsuccess = () => {
7 if (request.result) {
8 console.log('User found:', request.result);
9 } else {
10 console.log('User not found');
11 }
12 };
13
14 request.onerror = () => {
15 console.error('Error retrieving user:', request.error);
16 };
17}
18
19// Example: Get a user by id
20request.onsuccess = function (event) {
21 const db = event.target.result;
22 getUserById(db, 1);
23};
- Skapa en skrivskyddad transaktion med
db.transaction()
. - Använd
store.get(id)
för att hämta data som motsvarar den angivna primärnyckeln. onsuccess
anropas när hämtning lyckas, och resultatet visas om det finns tillgängligt. Om det inte finns något resultat hanteras det som 'ingen motsvarande data'.
Använda indexsökning
Om du vill söka efter egenskaper som inte är primärnyckeln använder du indexet du skapade i förväg.
1function getUserByName(db, name) {
2 const transaction = db.transaction('users', 'readonly');
3 const store = transaction.objectStore('users');
4 const index = store.index('name');
5 const request = index.get(name);
6
7 request.onsuccess = () => {
8 if (request.result) {
9 console.log('User by name:', request.result);
10 } else {
11 console.log('No user found with that name');
12 }
13 };
14
15 request.onerror = () => {
16 console.error('Error retrieving user by name:', request.error);
17 };
18}
19
20// Example: Get a user by id
21request.onsuccess = function (event) {
22 const db = event.target.result;
23 getUserByName(db, 'Alice');
24};
- Få åtkomst till det förskapade indexet
name
medstore.index('name')
. index.get(value)
hämtar den första posten med motsvarande värde. Om flera poster har samma värde kan du hämta dem alla medindex.getAll(value)
.
Uppdatera data
För att uppdatera befintlig data (skriva över) använder du metoden put()
.
Om det finns en post med motsvarande primärnyckel uppdateras den, annars läggs en ny post till.
1function updateUser(db, updatedUser) {
2 const transaction = db.transaction('users', 'readwrite');
3 const store = transaction.objectStore('users');
4 const request = store.put(updatedUser);
5
6 request.onsuccess = () => {
7 console.log('User updated:', updatedUser);
8 };
9
10 request.onerror = () => {
11 console.error('Update failed:', request.error);
12 };
13}
14
15// Example: Update user
16request.onsuccess = async (event) => {
17 const db = event.target.result;
18
19 // Test : update existing user
20 updateUser(db, { id: 3, name: 'John Updated' });
21
22 // Test : insert new user
23 updateUser(db, { id: 4, name: 'Charlie' });
24};
put()
är en praktisk metod som stödjer både uppdatering och tillägg.- Om data med samma primärnyckel redan finns kommer den att skrivas över.
- Om du vill kontrollera om data finns innan du uppdaterar kan du använda
get()
för att bekräfta i förväg.
Ta bort data
För att ta bort data som motsvarar en viss primärnyckel använder du metoden delete()
.
1function deleteUser(db, id) {
2 const transaction = db.transaction('users', 'readwrite');
3 const store = transaction.objectStore('users');
4 const request = store.delete(id);
5
6 request.onsuccess = () => {
7 console.log(`User with id=${id} deleted successfully`);
8 };
9
10 request.onerror = () => {
11 console.error(`Failed to delete user with id=${id}:`, request.error);
12 };
13}
14
15// Example: Delete a user by id
16request.onsuccess = function (event) {
17 const db = event.target.result;
18 deleteUser(db, 4);
19};
- Använd
store.delete(id)
för att ta bort data med motsvarande primärnyckel. - Observera att även om datan inte finns kommer inget fel att inträffa och operationen anses lyckad.
- Att implementera felhantering resulterar i mer robust kod.
Hämta all data
getAll()
För att hämta alla poster i objektbutiken använder du metoden getAll()
.
1function getAllUsers(db) {
2 const transaction = db.transaction('users', 'readonly');
3 const store = transaction.objectStore('users');
4 const request = store.getAll();
5
6 request.onsuccess = () => {
7 console.log('All users:', request.result);
8 };
9
10 request.onerror = () => {
11 console.error('Failed to retrieve users:', request.error);
12 };
13}
14
15// Example: Get all users
16request.onsuccess = function (event) {
17 const db = event.target.result;
18 getAllUsers(db);
19};
getAll()
hämtar alla poster i den angivna objektbutiken som en array.- Även när du hämtar stora mängder data på en gång behandlas det effektivt.
- Resultaten lagras som en array i
request.result
. - Lägg alltid till felhantering så att du kan hantera misslyckanden.
openCursor()
openCursor()
är en metod för att sekventiellt traversera poster i en objektbutik eller ett index. Den är användbar när du vill behandla data en post i taget istället för att hämta allt på en gång.
1function getAllUsersWithCursor(db) {
2 const transaction = db.transaction('users', 'readonly');
3 const store = transaction.objectStore('users');
4 const request = store.openCursor();
5
6 request.onsuccess = () => {
7 const cursor = request.result;
8 if (cursor) {
9 console.log('User:', cursor.value); // Process the current record
10 cursor.continue(); // Move to the next record
11 } else {
12 console.log('All users have been processed.');
13 }
14 };
15
16 request.onerror = () => {
17 console.error('Failed to open cursor:', request.error);
18 };
19}
20
21// Example: Get all users
22request.onsuccess = function (event) {
23 const db = event.target.result;
24 getAllUsersWithCursor(db);
25};
- Genom att använda
openCursor()
startar du en cursor och hämtar alla poster i objektbutiken en efter en. - Använd
cursor.value
för att få datan för den aktuella posten. - Flytta cursern till nästa post med
cursor.continue()
. - När
cursor === null
har alla poster traverserats.
Exempel på en uppdateringsprocess med openCursor()
Till exempel skulle processen för att ändra namnet på en användare vars name
är Alice
till Alicia
se ut så här:.
1function updateUserName(db, oldName, newName) {
2 const transaction = db.transaction('users', 'readwrite');
3 const store = transaction.objectStore('users');
4 const index = store.index('name'); // Use the 'name' index
5 const request = index.openCursor(IDBKeyRange.only(oldName));
6
7 request.onsuccess = () => {
8 const cursor = request.result;
9 if (cursor) {
10 const user = cursor.value;
11 user.name = newName; // Update the name
12 const updateRequest = cursor.update(user);
13
14 updateRequest.onsuccess = () => {
15 console.log('Updated user:', user);
16 };
17
18 updateRequest.onerror = () => {
19 console.error('Failed to update user:', updateRequest.error);
20 };
21
22 cursor.continue();
23 } else {
24 console.log('All matching users have been updated.');
25 }
26 };
27
28 request.onerror = () => {
29 console.error('Cursor error:', request.error);
30 };
31}
32
33// Example: Update user name
34request.onsuccess = function (event) {
35 const db = event.target.result;
36 updateUserName(db, 'Alice', 'Alicia');
37};
-
IDBKeyRange.only(oldName)
Genom att användaIDBKeyRange.only
kan du rikta in dig på endast de poster vars nyckel exakt matcharoldName
. Detta är användbart när du vill komma åt ett specifikt värde direkt. -
cursor.update()
Efter att ha uppdateratcursor.value
kommer ett anrop avupdate()
att skriva över motsvarande post. -
Hantering av flera träffar Genom att anropa
cursor.continue()
kan du flytta markören till nästa matchande post. Detta låter dig bearbeta flera poster som matchar samma nyckel eller villkor i följd. -
Felsökning Genom att skriva ut loggar i
onerror
när en process misslyckas blir det enklare att undersöka orsaker och felsöka under drift.
Exempel på en raderingsprocess med openCursor()
Till exempel skulle processen för att radera alla användare vars name
är Bob
se ut så här:.
1function deleteUsersByName(db, targetName) {
2 const transaction = db.transaction('users', 'readwrite');
3 const store = transaction.objectStore('users');
4 const index = store.index('name');
5 const request = index.openCursor(IDBKeyRange.only(targetName));
6
7 request.onsuccess = () => {
8 const cursor = request.result;
9 if (cursor) {
10 const deleteRequest = cursor.delete();
11
12 deleteRequest.onsuccess = () => {
13 console.log('Deleted user:', cursor.value);
14 };
15
16 deleteRequest.onerror = () => {
17 console.error('Failed to delete user:', deleteRequest.error);
18 };
19
20 cursor.continue();
21 } else {
22 console.log('All matching users have been deleted.');
23 }
24 };
25
26 request.onerror = () => {
27 console.error('Cursor error:', request.error);
28 };
29}
30
31// Example: Delete user by name
32request.onsuccess = function (event) {
33 const db = event.target.result;
34 deleteUsersByName(db, 'Bob');
35};
cursor.delete()
Medcursor.delete()
raderas posten på markörens nuvarande position. Eftersom resultatet returneras asynkront kan du kontrollera processen ionsuccess
.
Anteckningar om transaktioner och asynkron behandling
IndexedDB
är asynkront och händelsestyrt. Alla operationer måste hanteras med onsuccess
- eller onerror
-händelser. När du vill gruppera flera processer är det bekvämt att kapsla in dem i ett Promise
.
1function openDatabase() {
2 return new Promise((resolve, reject) => {
3 const request = indexedDB.open('MyDatabase', 1);
4
5 request.onupgradeneeded = function (event) {
6 const db = event.target.result;
7
8 // Initialization process (creating object stores and indexes, etc.)
9 const store = db.createObjectStore('users', { keyPath: 'id' });
10 store.createIndex('name', 'name', { unique: false });
11 };
12
13 request.onsuccess = function (event) {
14 const db = event.target.result;
15 resolve(db);
16 };
17
18 request.onerror = function () {
19 reject(request.error);
20 };
21 });
22}
23
24function addUserAsync(db, user) {
25 return new Promise((resolve, reject) => {
26 const transaction = db.transaction('users', 'readwrite');
27 const store = transaction.objectStore('users');
28 const request = store.add(user);
29
30 request.onsuccess = () => resolve();
31 request.onerror = () => reject(request.error);
32 });
33}
34
35async function main() {
36 try {
37 const db = await openDatabase();
38 await addUserAsync(db, { id: 1, name: 'Alice' });
39 console.log('User added successfully');
40 } catch (error) {
41 console.error('Error:', error);
42 }
43}
- Att kapsla in funktioner som
openDatabase
elleraddUserAsync
medPromise
gör det möjligt att hantera asynkrona processer intuitivt medasync/await
. - Detta undviker callback-helvetet och gör koden mer läsbar.
Sammanfattning
IndexedDB
är en mycket kraftfull funktion när du vill utföra avancerad datahantering på klientsidan. Till en början kan du bli förvirrad av händelsebaserad asynkron behandling, men när du väl förstår strukturen kan du utföra fullskaliga dataoperationer på klientsidan.
Särskilt genom att tänka på följande punkter kan du använda det smidigare:.
- Utför initial konfiguration med
onupgradeneeded
. - Var uppmärksam på transaktionernas läs-/skrivläge.
- Index möjliggör effektiv sökning.
- Data kan lagras som objekt, vilket gör det mycket kompatibelt med JSON.
Genom att behärska IndexedDB
blir datahantering för PWA:er och offline-appar mycket enklare.
Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.