JavaScript dan IndexedDB

JavaScript dan IndexedDB

Dalam artikel ini, kami akan menerangkan JavaScript dan IndexedDB.

Tutorial ini memberikan penjelasan langkah demi langkah tentang JavaScript dan IndexedDB, termasuk contoh kod praktikal di setiap langkah untuk membantu memperdalam pemahaman anda.

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 dan IndexedDB

IndexedDB ialah sebuah pangkalan data simpanan pasangan kunci-nilai secara tak segerak yang terbina dalam pelayar. Ia menawarkan ciri-ciri seumpama pangkalan data relasi dan membenarkan anda menyimpan serta mencari sejumlah besar data berstruktur di bahagian klien. Ia amat berguna untuk aplikasi yang boleh berfungsi secara luar talian dan PWA (Progressive Web Apps).

Ciri-ciri IndexedDB

  • Beroperasi secara tak segerak dan dipacu oleh peristiwa.
  • Objek JavaScript boleh disimpan dalam object store.
  • Carian melalui pertanyaan atau indeks adalah mungkin.
  • Ia mempunyai kapasiti simpanan yang besar (beratus-ratus MB atau lebih), membolehkan anda menyimpan lebih banyak data berbanding kuki atau localStorage.

Membuka dan Mencipta Pangkalan Data

Untuk menggunakan IndexedDB, anda mesti membuka dahulu pangkalan data. Jika ia tidak wujud, ia akan dicipta secara automatik.

1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
  • Kaedah open membuka pangkalan data secara tak segerak dan mencetuskan tiga peristiwa berikut.

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};
  • Peristiwa onsuccess dicetuskan apabila pangkalan data berjaya dibuka. Operasi seterusnya perlu dilakukan menggunakan request.result, yang kini boleh diakses pada tahap ini.

onerror

1// Fired when database fails to open
2request.onerror = (event) => {
3    console.error('Failed to open database:', event.target.error);
4};
  • Peristiwa onerror dicetuskan apabila pangkalan data gagal dibuka. Log ralat dan pengendalian ralat perlu dilakukan di sini.

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 dicetuskan apabila pangkalan data baru dicipta atau apabila versi yang dinyatakan lebih tinggi dari versi semasa. Penciptaan jadual (object store) dan takrifan skema perlu dilakukan pada masa ini.

Mencipta Object Store

Mula-mula, cipta 'object store' (setara dengan jadual) di dalam 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};

Di sini, tetapan berikut digunakan:.

  • createObjectStore createObjectStore ialah kaedah untuk mencipta object store baru dalam pangkalan data. Anda boleh menyimpan rekod dalam object store dan menentukan cara data diurus dengan menentukan keyPath dan pilihan lain. Proses ini mesti dilakukan di dalam peristiwa onupgradeneeded.

  • keyPath: 'id' Tetapkan keyPath kepada sifat id, yang secara unik mengenal pasti setiap rekod sebagai kunci primer. Ini membolehkan id digunakan secara automatik semasa menambah, mencari, atau mengemas kini data.

  • createIndex Gunakan kaedah createIndex untuk mencipta indeks berdasarkan sifat name untuk carian. Dengan menetapkan unique: false, rekod berganda dengan name yang sama dibenarkan.

Pengendalian Sambungan Pangkalan Data yang Berjaya

Tugaskan proses untuk dilaksanakan apabila sambungan pangkalan data berjaya pada peristiwa onsuccess. Dalam proses ini, anda memperoleh instance pangkalan data dan membuat persiapan untuk membaca serta menulis 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};
  • Proses ini dijalankan apabila sambungan pangkalan data IndexedDB berjaya diselesaikan.

  • event.target.result mengandungi instance pangkalan data yang telah dibuka (objek IDBDatabase), yang digunakan untuk memulakan transaksi dan mengakses object store.

  • Operasi baca dan tulis sebenar seperti menambah, mengambil, mengemas kini dan memadam data dilakukan menggunakan objek db.

Pada tahap ini, pangkalan data sudah sedia, jadi anda boleh memulakan transaksi dengan selamat.

Menambah Data

Berikut adalah cara menambah data baru ke dalam 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};
  • Cipta transaksi menggunakan db.transaction() dan tentukan object store yang ingin digunakan serta modnya (dalam kes ini, readwrite).
  • Tambah data baru menggunakan kaedah store.add().
  • Transaksi dilakukan secara automatik, tetapi jika anda ingin mengumpulkan beberapa operasi bersama, anda boleh menguruskannya dengan peristiwa tamat transaksi.

Mengambil Data (Carian Kunci Primer)

Berikut adalah cara mengambil data tertentu menggunakan kunci primer.

 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};
  • Cipta transaksi baca sahaja menggunakan db.transaction().
  • Guna store.get(id) untuk mengambil data yang sepadan dengan kunci primer yang ditentukan.
  • onsuccess akan dipanggil apabila pengambilan data berjaya, dan keputusan akan dipaparkan jika ada. Jika tiada keputusan, ia dianggap sebagai 'tiada data yang sepadan.'.

Menggunakan Carian Indeks

Jika anda ingin mencari berdasarkan sifat selain kunci primer, gunakan indeks yang telah anda cipta terlebih dahulu.

 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};
  • Akses indeks name yang telah dibuat menggunakan store.index('name').
  • index.get(value) mengambil rekod pertama dengan nilai yang sepadan. Jika terdapat lebih daripada satu rekod dengan nilai yang sama, anda boleh mengambil kesemuanya menggunakan index.getAll(value).

Mengemas Kini Data

Untuk mengemas kini data sedia ada (menulis ganti), guna kaedah put().

Jika rekod dengan kunci primer yang sepadan wujud, ia akan dikemas kini; jika tidak, rekod baru akan ditambah.

 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() ialah kaedah yang mudah kerana menyokong kedua-dua kemas kini dan penambahan.
  • Jika data dengan kunci primer yang sama sudah ada, ia akan ditulis ganti.
  • Jika anda mahu menyemak kewujudan data sebelum mengemas kini, anda boleh gunakan get() untuk pengesahan terlebih dahulu.

Memadam Data

Untuk memadam data yang sepadan dengan kunci primer tertentu, guna kaedah 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};
  • Guna store.delete(id) untuk memadam data dengan kunci primer yang sepadan.
  • Perlu diingat, walaupun data tersebut tidak wujud, tiada ralat akan berlaku dan ia tetap dianggap berjaya.
  • Pelaksanaan pengendalian ralat akan menghasilkan kod yang lebih kukuh.

Mengambil Semua Data

getAll()

Untuk mengambil semua rekod dalam object store, gunakan kaedah 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() mengambil semua rekod dalam object store yang dinyatakan sebagai tatasusunan.
  • Walaupun mengambil sejumlah besar data sekaligus, proses ini dijalankan dengan cekap.
  • Keputusan disimpan sebagai tatasusunan di dalam request.result.
  • Sentiasa tambahkan pengendalian ralat supaya anda dapat menangani kegagalan.

openCursor()

openCursor() ialah kaedah untuk melalui rekod secara berurutan dalam object store atau indeks. Ia berguna apabila anda ingin memproses data satu demi satu bukannya mengambil semuanya sekaligus.

 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};
  • Dengan menggunakan openCursor(), anda memulakan kursor dan mengambil semua rekod dalam object store satu demi satu.
  • Gunakan cursor.value untuk mendapatkan objek data bagi rekod semasa.
  • Gerakkan kursor ke rekod seterusnya menggunakan cursor.continue().
  • Apabila cursor === null, semua rekod telah dilalui.

Contoh proses kemaskini menggunakan openCursor()

Sebagai contoh, proses menukar nama pengguna yang name nya adalah Alice kepada Alicia akan kelihatan seperti ini:.

 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) Dengan menggunakan IDBKeyRange.only, anda boleh menumpukan hanya pada rekod yang kunci tepat sama dengan oldName. Ini berguna apabila anda ingin mengakses nilai tertentu secara langsung.

  • cursor.update() Selepas mengemaskini cursor.value, memanggil update() akan menimpa rekod yang berkaitan.

  • Mengendalikan Padanan Berganda Dengan memanggil cursor.continue(), anda boleh mengalihkan kursor ke rekod padanan seterusnya. Ini membolehkan anda memproses beberapa rekod yang sepadan dengan kunci atau syarat yang sama secara berturutan.

  • Pengendalian Ralat Dengan menghasilkan log dalam onerror apabila proses gagal, ia menjadi lebih mudah untuk menyiasat punca dan menyelesaikan masalah semasa operasi.

Contoh proses pemadaman menggunakan openCursor()

Sebagai contoh, proses memadam semua pengguna yang name nya adalah Bob akan kelihatan seperti ini:.

 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() Dengan cursor.delete(), rekod pada kedudukan kursor semasa akan dipadam. Oleh kerana keputusan dikembalikan secara asinkron, anda boleh menyemak proses itu dalam onsuccess.

Nota tentang Transaksi dan Pemprosesan Asinkron

IndexedDB adalah asinkron dan dipacu oleh peristiwa. Semua operasi mesti dikendalikan melalui acara onsuccess atau onerror. Apabila anda ingin mengumpulkan beberapa proses, adalah mudah untuk membungkusnya dalam 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}
  • Membungkus fungsi seperti openDatabase atau addUserAsync dengan Promise membolehkan pemprosesan asinkron diurus secara intuitif menggunakan async/await.
  • Ini mengelakkan masalah 'callback hell' dan menjadikan kod lebih mudah dibaca.

Ringkasan

IndexedDB adalah ciri yang sangat berkuasa apabila anda ingin melakukan pengurusan data lanjutan di bahagian pelayar. Pada mulanya, anda mungkin keliru dengan pemprosesan asinkron berasaskan peristiwa, tetapi setelah anda memahami strukturnya, anda boleh melakukan operasi data berskala penuh di sisi klien.

Khususnya, mengingati perkara-perkara berikut akan membantu anda menggunakannya dengan lebih lancar:.

  • Lakukan tetapan awal dengan onupgradeneeded.
  • Berikan perhatian kepada mod baca/tulis dalam transaksi.
  • Indeks membolehkan carian yang cekap.
  • Data boleh disimpan sebagai objek, menjadikannya sangat serasi dengan JSON.

Dengan menguasai IndexedDB, pengurusan data untuk PWA dan aplikasi luar talian menjadi lebih mudah.

Anda boleh mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Sila lihat juga saluran YouTube kami.

YouTube Video