JavaScript och IndexedDB

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 &amp; 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 av request.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 specificera keyPath och andra inställningar. Denna process måste göras inom ramen för onupgradeneeded-händelsen.

  • keyPath: 'id' Ange keyPath till egenskapen id, vilket unikt identifierar varje post som en primärnyckel. Detta gör att id automatiskt används vid tillägg, sökning eller uppdatering av data.

  • createIndex Använd metoden createIndex för att skapa ett index baserat på egenskapen name för sökningar. Genom att sätta unique: false tillåts flera poster med samma name.

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 fall readwrite).
  • 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 med store.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 med index.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ända IDBKeyRange.only kan du rikta in dig på endast de poster vars nyckel exakt matchar oldName. Detta är användbart när du vill komma åt ett specifikt värde direkt.

  • cursor.update() Efter att ha uppdaterat cursor.value kommer ett anrop av update() 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() Med cursor.delete() raderas posten på markörens nuvarande position. Eftersom resultatet returneras asynkront kan du kontrollera processen i onsuccess.

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 eller addUserAsync med Promise gör det möjligt att hantera asynkrona processer intuitivt med async/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.

YouTube Video