عند بناء صفحات ويب تفاعلية باستخدام HTML و JavaScript، غالباً ما نحتاج إلى الاحتفاظ ببعض بيانات المستخدم داخل المتصفح لفترة زمنية محددة، مثل تفضيلات المظهر، أو محتويات سلة التسوق، أو حتى حالة تسجيل الدخول المؤقتة. في الماضي، كانت ملفات تعريف الارتباط (Cookies) هي الخيار الوحيد المتاح، لكنها صُممت أساساً لتتبادل البيانات مع الخادم، وغالباً ما تكون محدودة السعة وتؤثر على أداء الشبكة بسبب إرسالها مع كل طلب HTTP. مع تطور المعايير البرمجية، ظهرت واجهة برمجة التطبيقات Web Storage API لتوفر حلًا أنظف وأكثر كفاءة لتخزين البيانات بشكل محلي في متصفح المستخدم دون الحاجة لإرسالها مع كل طلب.
في هذا الدرس الشامل، سنتعمق في كيفية استخدام هذه الواجهة بفعالية، والفرق بين نوعي التخزين المتاحين فيها، وكيفية توظيفها في مشاريعك الحقيقية مع تجنب الأخطاء الشائعة.
مفهوم Web Storage API والفرق بين LocalStorage و SessionStorage
تنقسم واجهة التخزين المحلي في المتصفح إلى كائنين رئيسيين (Objects)، كلاهما يتيح تخزين البيانات على هيئة أزواج من المفتاح والقيمة (Key-Value Pairs)، وتكون القيمة دائماً على شكل نص (String). الفرق الأساسي بينهما يكمن في مدة بقاء البيانات ونطاق توفرها:
- localStorage: يُستخدم لتخزين البيانات لفترة زمنية غير محدودة. البيانات لا تُحذف عند إغلاق المتصفح أو إعادة تشغيل الجهاز، وتظل محفوظة حتى يقوم المستخدم بمسح ذاكرة التخزين المؤقت للمتصفح يدوياً أو يقوم الكود البرمجي بحذفها صراحة.
- sessionStorage: يُستخدم لتخزين البيانات لجلسة عمل واحدة فقط (Session). تظل البيانات متاحة طالما أن علامة التبويب (Tab) أو نافذة المتصفح مفتوحة. بمجرد إغلاق النافذة، تُحذف البيانات نهائياً من الذاكرة.
كلا الكائنين يتبعان نفس الأساليب (Methods) للتعامل مع البيانات، مما يجعل الانتقال بين استخدام localStorage و sessionStorage أمراً بالغ السهولة بمجرد فهم الآلية الأساسية.
الخصائص الأساسية للتعامل مع التخزين المحلي
للتعامل مع البيانات تخزينًا واسترجاعًا، نوظف الدوال الأساسية التالية المتاحة في الكائنين:
- setItem(key, value): لإضافة عنصر جديد أو تحديث قيمة عنصر موجود مسبقاً، حيث يمثل key اسم المفتاح وvalue القيمة النصية المراد تخزينها.
- getItem(key): لاسترجاع القيمة المرتبطة بمفتاح معين عبر تمرير اسم المفتاح.
- removeItem(key): لحذف عنصر محدد بناءً على مفتاحه.
- clear(): لحذف جميع البيانات المخزنة داخل النطاق (Storage) بالكامل دفعة واحدة.
- key(index): للحصول على اسم المفتاح الموجود في ترتيب معين داخل التخزين.
- length: خاصية لمعرفة عدد العناصر المخزنة حالياً في النطاق.
مثال عملي متكامل: تطبيق حفظ تفضيلات المستخدم
لنفتض أننا نريد بناء صفحة ويب تتيح للمستخدم اختيار وتخزين لون الخلفية المفضل لديه (وضع داكن أو فاتح)، بحيث يتم تذكر هذا الاختيار حتى لو قام المستخدم بإغلاق الصفحة وفتحها في وقت لاحق. سنستخدم هنا هيكل HTML بسيط مع شيفرة JavaScript تفاعلية.
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<title>تخزين البيانات محلياً</title>
<style>
body { font-family: Arial, sans-serif; padding: 40px; transition: background-color 0.3s, color 0.3s; }
.light-mode { background-color: #ffffff; color: #333333; }
.dark-mode { background-color: #1a1a1a; color: #f1f1f1; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; margin-top: 20px; }
</style>
</head>
<body class="light-mode">
<h2>لوحة التحكم بالتفضيلات المحلية</h2>
<p>اختر المظهر المفضل لديك وسيقوم المتصفح بحفظه تلقائياً:</p>
<button id="toggleThemeBtn">تغيير المظهر</button>
<script>
const bodyElement = document.body;
const toggleBtn = document.getElementById('toggleThemeBtn');
// استرجاع القيمة المخزنة مسبقاً عند تحميل الصفحة
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
bodyElement.className = savedTheme;
}
// التعامل مع حدث النقر لتغيير وحفظ المظهر
toggleBtn.addEventListener('click', function() {
if (bodyElement.classList.contains('light-mode')) {
bodyElement.className = 'dark-mode';
localStorage.setItem('theme', 'dark-mode');
} else {
bodyElement.className = 'light-mode';
localStorage.setItem('theme', 'light-mode');
}
});
</script>
</body>
</html>شرح المثال العملي
في هذا المثال، قم بتطبيق الخطوات التالية:
- عرّفنا فئتين في تنسيقات CSS (Classes) للمظهر:
light-modeللمظهر الفاتح وdark-modeللمظهر الداكن. - عند فتح الصفحة، يتحقق الكود مباشرةً مما إذا كان هناك مفتاح باسم
themeمخزناً مسبقاً في localStorage باستخدام الدالةlocalStorage.getItem('theme'). - إذا وُجدت قيمة مخزنة، يتم تطبيقها مباشرة على عنصر
<body>لضمان استمرار المظهر الذي اختاره المستخدم بدلاً من العودة للقيمة الافتراضية. - عند الضغط على الزر، يتحقق الكود من المظهر الحالي، ويقوم بتبديله، ثم يحفظ القيمة الجديدة فوراً في التخزين المحلي باستخدام
localStorage.setItem('theme', 'value').
التعامل مع الكائنات المعقدة (Objects & Arrays)
كما ذكرنا سابقاً، فإن Web Storage يقبل النصوص (Strings) فقط كقيم. ماذا لو أردنا تخزين مصفوفة من البيانات أو كائن يحتوي على عدة خصائص (مثل بيانات مستخدم، تفضيلات متقدمة، أو عناصر سلة تسوق)؟
الحل هو تحويل الكائن إلى نص بصيغة JSON باستخدام الدالة JSON.stringify() قبل تخزينه، وعند استرجاعه نقوم بتحويله مرة أخرى إلى كائن برمجي باستخدام الدالة JSON.parse().
// تخزين كائن برمجي يحتوي على إعدادات متعددة
const userSettings = {
username: "أحمد",
notifications: true,
fontSize: "large"
};
// تحويل الكائن إلى نص وتخزينه في LocalStorage
localStorage.setItem("settings", JSON.stringify(userSettings));
// استرجاع البيانات وتحويلها مرة أخرى إلى كائن قابل للاستخدام
const retrievedData = localStorage.getItem("settings");
const settingsObj = JSON.parse(retrievedData);
console.log(settingsObj.username); // يطبع: أحمد
console.log(settingsObj.notifications); // يطبع: trueالقيود والأمان في Web Storage
على الرغم من سهولة استخدام Web Storage API ومرونتها العالية، إلا أن هناك قيوداً واعتبارات أمنية هامة يجب مراعاتها بعناية عند تطوير تطبيقات الويب:
- السعة التخزينية: يمتلك كل نطاق (Domain) مساحة تخزينية محددة تختلف باختلاف المتصفحات، وغالباً ما تكون بحدود 5 ميجابايت لكل نطاق. هذه المساحة كافية جداً للنصوص والبيانات البسيطة، وليست مخصصة لتخزين ملفات وسائط ضخمة كالفيديو أو الصور الكبيرة.
- الأمان والخصوصية: البيانات المخزنة في التخزين المحلي غير مشفرة وقابلة للقرأة بالكامل من قبل أي سكريبت (JavaScript) يعمل على نفس النطاق. لذلك، لا تقم أبداً بتخزين معلومات حساسة مثل كلمات المرور أو الرموز السرية للغاية (Tokens) في
localStorage، حيث يمكن لأي هجمة تعتمد على حقن السكريبات (XSS) قراءة هذه البيانات وسحبها بسهولة. - المزامنة والتزامن: عمليات القراءة والكتابة في Web Storage تتم بشكل متزامن (Synchronous)، مما يعني أن العمليات الكبيرة جداً قد تتسبب في إبطاء تنفيذ الكود وإحداث تجميد مؤقت إذا تم تنفيذها بكثافة عالية في خيط المعالجة الرئيسي (Main Thread).
- نطاق الأصل (Same-Origin Policy): البيانات المخزنة معزولة تماماً بين النطاقات المختلفة. موقع (A) لا يمكنه بأي حال من الأحوال قراءة البيانات المخزنة بواسطة موقع (B)، مما يضمن عزلاً جيداً بين التطبيقات على المتصفح.
الأخطاء الشائعة عند التعامل مع التخزين المحلي
أثناء كتابة الكود البرمجي، يقع المطورون أحياناً في بعض الأخطاء المتكررة التي تؤدي إلى سلوك غير متوقع أو توقف التطبيق:
- نسيان التعامل مع حالة عدم وجود البيانات: عند استخدام
getItem()لمفتاح غير موجود بالأساس، تعود الدالة بالقيمةnull. محاولة قراءة خصائص منnullستؤدي مباشرة إلى توقف السكريبت عن العمل وحدوث خطأ (TypeError). يجب دائماً التحقق من وجود القيمة قبل استخدامها أو توفير قيمة افتراضية بديلة. - تخزين القيم بدون تحويل: محاولة تخزين كائن أو مصفوفة مباشرة بدون استخدام الدالة
JSON.stringify()ستحول الكائن تلقائياً إلى النص الافتراضي"[object Object]"، مما يؤدي إلى ضياع البيانات الأصلية بالكامل. - الاعتماد المفرط كقاعدة بيانات: استخدام التخزين المحلي كبديل لقاعدة بيانات حقيقية لتخزين البيانات الحساسة أو الكبيرة، متجاهلاً حقيقة أن المستخدم يستطيع مسح هذه البيانات في أي لحظة.
خلاصة الدرس
تُعد واجهة Web Storage API أداة قوية وفعالة لتحسين تجربة المستخدم عبر حفظ التفضيلات والبيانات الخفيفة مباشرة داخل المتصفح. تتيح لك المقارنة بين localStorage للبيانات الدائمة و sessionStorage للبيانات المؤقتة مرونة عالية في تصميم التطبيقات والصفحات التفاعلية.
مع مراعاة المعايير الأمنية، وتجنب حفظ البيانات الحساسة، والتحقق دائماً من وجود البيانات المسترجعة، يمكنك توظيف هذه التقنية بثقة لبناء صفحات ويب أكثر ذكاءً واستجابة لمتطلبات مستخدميك.