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 יכולים להישמר בתוך מחסני אובייקטים.
- ניתן לבצע חיפוש באמצעות שאילתות או אינדקסים.
- יש לו קיבולת אחסון גדולה (מאות מגה-בייט ואף יותר), מה שמאפשר לאחסן הרבה יותר נתונים מאשר בעוגיות או 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
מופעל כאשר מסד הנתונים נוצר לראשונה או כאשר הגרסה שצוינה גבוהה מהגרסה הנוכחית. בשלב זה יש ליצור טבלאות (מחסני אובייקטים) ולהגדיר את הסכימה.
יצירת מחסן אובייקטים
ראשית, צור 'מחסן אובייקטים' (שווה ערך לטבלה) בתוך האירוע 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
היא מתודה ליצירת מחסן אובייקטים חדש במסד הנתונים. ניתן לאחסן רשומות במחסן ולשלוט בניהול נתונים על ידי הגדרת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()
לשליפת כל הרשומות ממחסן האובייקטים, השתמש במתודה 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()
שולפת את כל הרשומות מהמחסן כמערך.- גם בשליפת כמויות גדולות של נתונים יחד, התהליך מתבצע ביעילות.
- התוצאות נאגרות כמערך בתוך
request.result
. - הוסף תמיד טיפול בשגיאות כדי להתמודד עם תקלות.
openCursor()
openCursor()
היא מתודה לחצית רשומות ברצף בתוך מחסן אובייקטים או אינדקס. זה שימושי כאשר יש לעבד נתונים אחד-אחד במקום לשלוף הכל בפעולה אחת.
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()
תוכל להתחיל סמן (cursor) ולשלוף את כל הרשומות אחת-אחת. - השתמש ב־
cursor.value
כדי לשלוף את נתוני הרשומה הנוכחית. - העברת הסמן לרשומה הבאה מתבצעת עם
cursor.continue()
. - כאשר
cursor === null
, כל הרשומות עברו עיבוד.
דוגמה לתהליך עדכון באמצעות openCursor()
לדוגמה, כך ייראה התהליך של שינוי השם של משתמש ששמו 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()
לדוגמה, התהליך של מחיקת כל המשתמשים ששמם 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 בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.