JavaScript og IndexedDB
I denne artikel forklarer vi JavaScript og IndexedDB.
Denne vejledning giver en trin-for-trin forklaring på JavaScript og IndexedDB
, inklusive praktiske eksempler på kode ved hvert trin for at hjælpe med at uddybe din forstå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 og IndexedDB
IndexedDB
er en asynkron nøgle-værdi-database, der er indbygget i browsere. Den tilbyder funktioner, der ligner relationelle databaser, og giver dig mulighed for at gemme og søge store mængder strukturerede data på klientsiden. Det er især nyttigt til offline-applikationer og PWA'er (Progressive Web Apps).
Funktioner i IndexedDB
- Fungerer på en asynkron og begivenhedsdrevet måde.
- JavaScript-objekter kan gemmes i objektlagre.
- Søgning med forespørgsler eller indekser er muligt.
- Den har en stor lagringskapacitet (hundreder af MB eller mere), så du kan gemme meget mere data end med cookies eller localStorage.
Åbning og oprettelse af en database
For at bruge IndexedDB
skal du først åbne en database. Hvis den ikke findes, bliver den oprettet automatisk.
1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
- Metoden
open
åbner databasen asynkront og udløser følgende 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};
- Hændelsen
onsuccess
udløses, når databasen åbnes med succes. Efterfølgende operationer skal udføres ved hjælp afrequest.result
, som nu er tilgængelig.
onerror
1// Fired when database fails to open
2request.onerror = (event) => {
3 console.error('Failed to open database:', event.target.error);
4};
- Hændelsen
onerror
udløses, når databasen ikke kan åbnes. Fejllogning og fejlhåndtering bør udføres her.
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
udløses, når databasen oprettes for første gang, eller når den angivne version er højere end den nuværende version. Oprettelse af tabeller (objektlagre) og definition af skemaet bør ske på dette tidspunkt.
Oprettelse af et objektlager
Først skal du oprette et 'objektlager' (svarende til en tabel) inden for 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};
Her anvendes følgende indstillinger:.
-
createObjectStore
createObjectStore
er en metode til at oprette et nyt objektlager i databasen. Du kan gemme poster i et objektlager og definere, hvordan data håndteres, ved at angivekeyPath
og andre indstillinger. Denne proces skal udføres inde i hændelsenonupgradeneeded
. -
keyPath: 'id'
IndstilkeyPath
til egenskabenid
, som entydigt identificerer hver post som primær nøgle. Dette gør det muligt at brugeid
automatisk ved tilføjelse, søgning eller opdatering af data. -
createIndex
Brug metodencreateIndex
til at oprette et indeks baseret på egenskabenname
til søgning. Ved at sætteunique: false
tillades flere poster med sammename
.
Håndtering af vellykkede databaseforbindelser
Tilknyt processer, der skal udføres ved en vellykket databaseforbindelse, til hændelsen onsuccess
. I denne proces opnår du databaseinstansen og forbereder læsning og skrivning af 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};
-
Denne proces udføres, når forbindelsen til
IndexedDB
-databasen lykkes. -
event.target.result
indeholder den åbnede databaseinstans (IDBDatabase
-objekt), som bruges til at starte transaktioner og tilgå objektlagre. -
Reelle læse- og skriveoperationer som at tilføje, hente, opdatere og slette data udføres ved hjælp af
db
-objektet.
På dette tidspunkt er databasen klar, så du kan sikkert starte transaktioner.
Tilføjelse af data
Sådan tilføjer du nye data til 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};
- Opret en transaktion med
db.transaction()
og angiv det objektlager, der skal arbejdes på, samt tilstanden (i dette tilfældereadwrite
). - Tilføj nye data ved hjælp af metoden
store.add()
. - Transaktioner gennemføres automatisk, men hvis du vil gruppere flere operationer, kan du styre dem ved at bruge transaktionsafslutningshændelser.
Hentning af data (primær nøgle-søgning)
Sådan hentes bestemte data ved hjælp af primær nøgle.
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};
- Opret en skrivebeskyttet transaktion med
db.transaction()
. - Brug
store.get(id)
for at hente data, der svarer til den angivne primære nøgle. onsuccess
kaldes, når hentningen lykkes, og resultatet vises, hvis det findes. Hvis der ikke er et resultat, behandles det som 'ingen tilsvarende data'.
Brug af indeks-søgning
Hvis du vil søge på andre egenskaber end primær nøgle, skal du bruge det indeks, du har oprettet på forhånd.
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å adgang til det oprettede
name
-indeks ved at brugestore.index('name')
. index.get(value)
henter den første post med den tilsvarende værdi. Hvis flere poster har den samme værdi, kan du hente dem alle medindex.getAll(value)
.
Opdatering af data
For at opdatere eksisterende data (overskrive) skal du bruge metoden put()
.
Hvis en post med en matchende primær nøgle findes, opdateres den; hvis ikke, tilføjes en ny post.
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()
er en praktisk metode, der understøtter både opdatering og tilføjelse.- Hvis data med samme primære nøgle allerede findes, vil de blive overskrevet.
- Hvis du vil tjekke for eksistens før opdatering, kan du bruge
get()
for at bekræfte dette på forhånd.
Sletning af data
For at slette data, der svarer til en angiven primær nøgle, skal du bruge 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};
- Brug
store.delete(id)
for at slette data med en tilsvarende primær nøgle. - Bemærk, at selvom dataene ikke findes, opstår der ikke nogen fejl, og det betragtes som en succes.
- Implementering af fejlhåndtering vil give mere robust kode.
Hentning af alle data
getAll()
For at hente alle poster i objektlageret, skal du bruge 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()
henter alle poster i det angivne objektlager som et array.- Selv når store mængder data hentes på én gang, behandles det effektivt.
- Resultaterne gemmes som et array i
request.result
. - Tilføj altid fejlhåndtering, så du kan håndtere fejl eller problemer.
openCursor()
openCursor()
er en metode til sekventiel gennemløb af poster i et objektlager eller indeks. Det er nyttigt, når du vil behandle data ét element ad gangen i stedet for at hente store mængder på én gang.
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};
- Ved at bruge
openCursor()
starter du en markør og henter alle poster i objektlageret én efter én. - Brug
cursor.value
for at få dataobjektet for den aktuelle post. - Flyt markøren til næste post med
cursor.continue()
. - Når
cursor === null
, er alle poster gennemløbet.
Eksempel på en opdateringsproces ved hjælp af openCursor()
For eksempel vil processen med at ændre navnet på en bruger, hvis name
er Alice
, til Alicia
se sådan ud:.
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)
Ved at brugeIDBKeyRange.only
kan du målrette kun de poster, hvis nøgle præcis matcheroldName
. Dette er nyttigt, når du vil tilgå en bestemt værdi direkte. -
cursor.update()
Efter opdatering afcursor.value
, vil et kald tilupdate()
overskrive den tilsvarende post. -
Håndtering af flere match Ved at kalde
cursor.continue()
, kan du flytte markøren til den næste matchende post. Dette gør det muligt at behandle flere poster, der matcher den samme nøgle eller betingelse i rækkefølge. -
Fejlhåndtering Ved at udskrive logfiler i
onerror
når en proces fejler, bliver det lettere at undersøge årsager og fejlfinde under drift.
Eksempel på en sletningsproces ved hjælp af openCursor()
For eksempel vil processen med at slette alle brugere, hvis name
er Bob
, se sådan ud:.
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()
slettes posten ved den aktuelle markørposition. Da resultatet returneres asynkront, kan du kontrollere processen ionsuccess
.
Bemærkninger om transaktioner og asynkron behandling
IndexedDB
er asynkron og hændelsesdrevet. Alle operationer skal håndteres med onsuccess
eller onerror
begivenheder. Når du vil samle flere processer sammen, er det praktisk at pakke dem ind i et 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}
- Ved at pakke funktioner som
openDatabase
elleraddUserAsync
ind i etPromise
, er det muligt at håndtere asynkrone processer intuitivt medasync/await
. - Dette undgår callback-helvede og gør koden mere læsbar.
Sammendrag
IndexedDB
er en meget kraftfuld funktion, når du vil udføre avanceret datastyring på browsersiden. I starten kan du blive forvirret over hændelsesbaseret asynkron behandling, men når du først forstår strukturen, kan du udføre fuldskala dataoperationer på klientsiden.
Især ved at holde følgende punkter i tankerne, kan du bruge det mere problemfrit:.
- Udfør initial opsætning med
onupgradeneeded
. - Vær opmærksom på læse-/skrive-tilstanden for transaktioner.
- Indekser muliggør effektiv søgning.
- Data kan gemmes som objekter, hvilket gør det meget kompatibelt med JSON.
Ved at mestre IndexedDB
bliver datastyring for PWA'er og offline apps meget nemmere.
Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.