جافاسكريبت و IndexedDB
في هذا المقال، سنشرح جافاسكريبت و IndexedDB۔
يوفر هذا الدليل شرحًا خطوة بخطوة لجافاسكريبت و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>
جافاسكريبت و IndexedDB
IndexedDB
هي قاعدة بيانات مفتاح-قيمة غير متزامنة مدمجة في المتصفحات۔ تقدم ميزات مشابهة لقواعد البيانات العلائقية وتسمح بتخزين والبحث ضمن كميات كبيرة من البيانات المنظمة على جانب العميل۔ وهي مفيدة بشكل خاص للتطبيقات التي تدعم العمل دون اتصال وتطبيقات الويب التقدمية (PWA)۔
ميزات IndexedDB
- تعمل بطريقة غير متزامنة وقائمة على الأحداث۔
- يمكن تخزين كائنات جافاسكريبت في متاجر الكائنات۔
- البحث ممكن باستخدام الاستعلامات أو الفهارس۔
- لديها سعة تخزين كبيرة (مئات الميجابايت وأكثر)، ما يسمح لك بتخزين بيانات أكثر بكثير من الكوكيز أو التخزين المحلي (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
، يُسمح بوجود سجلات متعددة بنفس الاسم۔
معالجة اتصالات قاعدة البيانات الناجحة
قم بتعيين العمليات التي تنفذ عند نجاح الاتصال بقاعدة البيانات إلى حدث 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.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
۔ - هذا يتجنب جحيم الاستدعاءات المتداخلة ويجعل الشفرة أكثر قابلية للقراءة۔
الملخص
IndexedDB
ميزة قوية جدًا عندما تريد إجراء إدارة بيانات متقدمة على جانب المتصفح۔ في البداية، قد تشعر بالحيرة من المعالجة غير المتزامنة المعتمدة على الأحداث، ولكن بمجرد أن تفهم البنية، يمكنك تنفيذ عمليات بيانات كاملة على جانب العميل۔
على وجه الخصوص، ستساعدك مراعاة النقاط التالية على استخدامه بشكل أكثر سلاسة:۔
- قم بتنفيذ الإعداد الأولي باستخدام
onupgradeneeded
۔ - انتبه إلى وضع القراءة/الكتابة للمعاملات۔
- الفهارس تتيح البحث بكفاءة۔
- يمكن تخزين البيانات ككائنات، مما يجعلها متوافقة جدًا مع JSON۔
بإتقانك لـIndexedDB
، تصبح إدارة البيانات لتطبيقات PWA والتطبيقات غير المتصلة أسهل بكثير۔
يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔