JavaScript и IndexedDB
В этой статье мы объясним JavaScript и IndexedDB.
В этом уроке дается пошаговое объяснение JavaScript и IndexedDB
, включая практические примеры кода на каждом этапе для лучшего понимания.
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 и IndexedDB
IndexedDB
— это асинхронная база данных ключ-значение, встроенная в браузеры. Она предоставляет функции, схожие с реляционными базами данных, и позволяет хранить и искать большие объемы структурированных данных на стороне клиента. Она особенно полезна для приложений, работающих офлайн, и прогрессивных веб-приложений (PWA).
Особенности IndexedDB
- Работает в асинхронном и событийно-управляемом режиме.
- JavaScript-объекты могут храниться в object stores (хранилищах объектов).
- Доступен поиск по запросам или индексам.
- Имеет большой объем хранения (сотни мегабайт и более), что позволяет хранить гораздо больше данных, чем cookies или localStorage.
Открытие и создание базы данных
Чтобы использовать IndexedDB
, необходимо сначала открыть базу данных. Если база данных не существует, она будет создана автоматически.
1const request = indexedDB.open('MyDatabase', 1); // Specify DB name and version
- Метод
open
асинхронно открывает базу данных и вызывает следующие три события.
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
срабатывает при успешном открытии базы данных. Дальнейшие операции следует выполнять, используяrequest.result
, который становится доступен в этот момент.
onerror
1// Fired when database fails to open
2request.onerror = (event) => {
3 console.error('Failed to open database:', event.target.error);
4};
- Событие
onerror
срабатывает при ошибке открытия базы данных. В этом месте следует реализовать логирование ошибок и обработку ошибок.
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
срабатывает при создании новой базы данных или если указана версия выше текущей. Создание таблиц (object stores) и определение схемы должно происходить в этот момент.
Создание object store (хранилища объектов)
Сначала создайте 'object store' (аналог таблицы) внутри события 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};
Здесь применяются следующие настройки:.
-
createObjectStore
createObjectStore
— это метод для создания нового object store в базе данных. Можно хранить записи в object store и определять способ хранения данных, указываяkeyPath
и другие опции. Этот процесс должен выполняться внутри событияonupgradeneeded
. -
keyPath: 'id'
УстановитеkeyPath
на свойствоid
, которое будет уникальным идентификатором (первичным ключом) каждой записи. Это позволяет использоватьid
автоматически при добавлении, поиске и обновлении данных. -
createIndex
Используйте методcreateIndex
для создания индекса по свойствуname
для поиска. При указанииunique: false
допускаются несколько записей с одинаковымname
.
Обработка успешного подключения к базе данных
Назначьте процессы, которые должны выполняться при успешном подключении к базе данных, на событие onsuccess
. В этом процессе вы получаете экземпляр базы данных и готовитесь к чтению и записи данных.
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};
-
Этот процесс выполняется при успешном подключении к базе данных
IndexedDB
. -
event.target.result
содержит экземпляр открытой базы данных (IDBDatabase
), который используется для начала транзакций и доступа к хранилищам объектов. -
Непосредственные операции чтения и записи, такие как добавление, получение, обновление и удаление данных, выполняются с помощью объекта
db
.
На этом этапе база данных готова, и можно безопасно начинать транзакции.
Добавление данных
Вот как добавить новые данные в 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};
- Создайте транзакцию с помощью
db.transaction()
и укажите хранилище объектов и режим (в данном случаеreadwrite
). - Добавьте новые данные с помощью метода
store.add()
. - Транзакции фиксируются автоматически, но если вы хотите сгруппировать несколько операций, можно контролировать их с помощью событий окончания транзакции.
Получение данных (поиск по первичному ключу)
Вот как получить конкретные данные по первичному ключу.
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};
- Создайте транзакцию только для чтения с помощью
db.transaction()
. - Используйте
store.get(id)
, чтобы получить данные по указанному первичному ключу. - Событие
onsuccess
срабатывает при успешном получении, и результат отображается, если он есть. Если результата нет, это считается как 'отсутствие соответствующих данных'.
Использование поиска по индексу
Если нужно искать по свойствам, отличным от первичного ключа, используйте заранее созданный индекс.
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};
- Обратитесь к ранее созданному индексу
name
с помощьюstore.index('name')
. index.get(value)
получает первую запись с соответствующим значением. Если несколько записей имеют одно и то же значение, вы можете получить их все с помощьюindex.getAll(value)
.
Обновление данных
Для обновления существующих данных (замены) используйте метод put()
.
Если запись с таким первичным ключом уже существует, она будет обновлена; если нет — будет добавлена новая запись.
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()
— это удобный метод, который поддерживает как обновление, так и добавление.- Если данные с таким же первичным ключом уже существуют, они будут перезаписаны.
- Если вы хотите проверить существование данных перед обновлением, используйте
get()
для предварительного подтверждения.
Удаление данных
Для удаления данных по указанному первичному ключу используйте метод 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};
- Используйте
store.delete(id)
, чтобы удалить данные с соответствующим первичным ключом. - Обратите внимание, что даже если данные не существуют, ошибка не возникнет, и операция считается успешной.
- Реализация обработки ошибок приведет к более стабильному коду.
Получение всех данных
getAll()
Чтобы получить все записи из object store, используйте метод 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()
возвращает все записи из указанного object store в виде массива.- Даже при получении большого объема данных за раз обработка происходит эффективно.
- Результаты сохраняются в виде массива в
request.result
. - Всегда добавляйте обработку ошибок, чтобы иметь возможность справляться с неудачами.
openCursor()
openCursor()
— это метод для последовательного обхода записей в object store или индексе. Это полезно, когда нужно обрабатывать данные по одному элементу, а не получать сразу большой объем данных.
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};
- С помощью
openCursor()
вы запускаете курсор и получаете все записи в object store одну за другой. - Используйте
cursor.value
, чтобы получить объект данных текущей записи. - Передвиньте курсор на следующую запись с помощью
cursor.continue()
. - Когда
cursor === null
, все записи были пройдены.
Пример процесса обновления с использованием openCursor()
Например, процесс изменения имени пользователя, у которого name
— Alice
, на Alicia
будет выглядеть так:.
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)
ИспользуяIDBKeyRange.only
, вы можете выбрать только те записи, ключ которых точно совпадает сoldName
. Это полезно, когда вы хотите напрямую обратиться к определённому значению. -
cursor.update()
После обновленияcursor.value
вызовupdate()
перезапишет соответствующую запись. -
Обработка нескольких совпадений Вызвав
cursor.continue()
, вы можете переместить курсор к следующей совпадающей записи. Это позволяет последовательно обрабатывать несколько записей, соответствующих одному и тому же ключу или условию. -
Обработка ошибок Вывод логов в
onerror
при ошибках облегчает поиск причин и устранение проблем во время работы.
Пример процесса удаления с использованием openCursor()
Например, процесс удаления всех пользователей с name
равным Bob
будет выглядеть следующим образом:.
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()
С помощьюcursor.delete()
запись в текущей позиции курсора удаляется. Так как результат возвращается асинхронно, вы можете проверить процесс вonsuccess
.
Примечания по транзакциям и асинхронной обработке
IndexedDB
работает асинхронно и управляется событиями. Все операции должны обрабатываться через события onsuccess
или onerror
. Если вы хотите сгруппировать несколько процессов, удобно обернуть их в 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}
- Обертывание функций вроде
openDatabase
илиaddUserAsync
вPromise
позволяет интуитивно обрабатывать асинхронные процессы с помощьюasync/await
. - Это помогает избежать 'callback hell' и делает код более читаемым.
Резюме
IndexedDB
— это очень мощная функция, если вы хотите управлять данными на стороне браузера на продвинутом уровне. Сначала асинхронная обработка, основанная на событиях, может показаться сложной, но как только вы разберётесь со структурой, вы сможете выполнять полноценные операции с данными на стороне клиента.
В частности, если учитывать следующие моменты, использование будет более эффективным:.
- Выполняйте начальную настройку с помощью
onupgradeneeded
. - Обращайте внимание на режим чтения/записи транзакций.
- Индексы обеспечивают эффективный поиск.
- Данные могут храниться как объекты, что обеспечивает высокую совместимость с JSON.
Освоив IndexedDB
, вы упростите управление данными в PWA и офлайн-приложениях.
Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.