اذهب إلى المحتوى

تخزين البيانات محليا في متصفح الويب عبر جافاسكربت


ابراهيم الخضور

يسمح هذان الكائنان بتخزين الأزواج "مفتاح/قيمة" في المتصفح، لكن الميزة الهامة لهما هي بقاء البيانات المخزنة في الكائن sessionStorage بعد تحديث الصفحة وبقاء المعلومات المخزنة في localStorage بعد إعادة تشغيل المتصفح، لكن السؤال الذي يلفت النظر هو: ما دام لدينا ملفات تعريف الارتباط cookies، فلماذا سنستخدم كائنات إضافيةً؟ والجواب:

  • لا يُرسَل كائنا تخزين بيانات الويب هذان إلى الخادم مع كل طلب، وذلك خلافًا لملفات تعريف الارتباط، وبالتالي سنتمكن من تخزين بيانات أكثر، حيث تتيح أغلب المتصفحات حوالي 2 ميغابايت من البيانات -وأكثر-، ولها إعدادات لتهيئة حجم التخزين.
  • لا يمكن للخادم التحكم بهذين الكائنين عبر ترويسات HTTP، وسيُنجز كل شيء باستخدام JavaScript، خلافًا لملفات تعريف الارتباط.
  • ترتبط ذاكرة التخزين بالأصل الذي ولّدها (نطاق/بروتوكول/منفذ)، مما يعني أن البروتوكولات أو النطاقات الفرعية المختلفة ستدل على كائنات تخزين مختلفة، ولا يمكن أن تصل إلى بيانات بعضها البعض.

لكائني التخزين التوابع والخصائص نفسها، وهي:

  • (setItem(key, value: يخزّن الأزواج "مفتاح/قيمة".
  • (getItem(key: يحصل على القيمة عن طريق المفتاح.
  • (removeItem(key: يزيل المفتاح مع قيمته.
  • ()clear: يحذف كل شيء.
  • (key(index: يعيد مفتاحًا ذا موقع محدد.
  • length: يعطي عدد العناصر المخزّنة.

تشبه هذه التوابع ما يقوم به الترابط Map، لكنه يسمح أيضًا بالوصول إلى المفاتيح من خلال مواقعها (key(index.

مثال نموذجي عن الكائن localStorage

ميزات هذا الكائن الرئيسية هي:

  • مشترك بين كل النوافذ التي تشترك بالأصل ذاته.
  • ليس للبيانات فترة صلاحية، إذ تبقى بعد إعادة تشغيل المتصفح أو نظام التشغيل.

فلو شغّلنا الشيفرة التالية مثلًا:

localStorage.setItem('test', 1);

إذا أغلقنا المتصفح ثم أعدنا تشغيله، أو فتحنا الصفحة نفسها في أكثر من نافذة، فسنحصل على القيمة التي خزناها بالشكل التالي:

alert( localStorage.getItem('test') ); // 1

علينا فقط أن نكون ضمن صفحات تنتمي إلى الأصل ذاته (نطاق/منفذ/بروتوكول)، على الرغم من إمكانية اختلاف المسار، لأن الكائن localStorage مشترك بين كل النوافذ التي تنتمي إلى الأصل ذاته.

الوصول بأسلوب الكائنات

يمكن استخدام أسلوب الكائن البسيط للحصول على المفاتيح أو تغيير قيمها بالشكل التالي:

// ضبط المفتاح
localStorage.test = 2;

// الحصول على قيمة المفتاح
alert( localStorage.test ); // 2

// إزالة المفتاح
delete localStorage.test;

يُسمح بهذه الطريقة -التي ستعمل غالبًا- لأسباب تاريخية، لكن لا يُفضّل استعمالها للأسباب التالية:

  1. لو ولّد المستخدم المفتاح، فقد يكون أي شيء مثل length أو toString، أي قد يتشابه مع توابع محجوزة تستخدَم مع localStorage، في هذه الحالة ستعمل التوابع getItem/setItem، لكن سيخفق الوصول بأسلوب الكائنات .
let key = 'length';
localStorage[key] = 5; // Error, can't assign length
  1. الحدث storage الذي يقع عند تعديل البيانات، وليس عند تطبيق أسلوب الكائنات، وسنرى ذلك لاحقًا في هذا المقال.

التنقل بين المفاتيح ضمن حلقات

تؤمّن التوابع السابقة وظائف الحصول على قيم المفاتيح وضبطها وحذفها، لكن كيف سنخزّن جميع القيم أو المفاتيح؟ لسوء الحظ لا تقبل كائنات تخزين البيانات التكرار، لكن إحدى الطرق المتبعة هي التنقل في حلقة كما لو أننا نتعامل مع مصفوفة:

for(let i=0; i<localStorage.length; i++) {
  let key = localStorage.key(i);
  alert(`${key}: ${localStorage.getItem(key)}`);
}

يمكن استخدام الحلقة for key in localStorage كما نفعل مع الكائنات النظامية، حيث تتكرر تعليمات الحلقة وفقًا للمفاتيح المخزّنة، لكنها ستعطي حقولًا مدمجةً غير مطلوبة:

// محاولة فاشلة
for(let key in localStorage) {
  alert(key); // وغيرها من الحقول المدمجة getItem, setItem ستظهر 
}

فإمّا أن نرشح الحقول التي ستُعرض بالتحقق من الخاصية hasOwnProperty:

for(let key in localStorage) {
  if (!localStorage.hasOwnProperty(key)) {
    continue; // "setItem", "getItem" تتجاهل مفاتيح مثل
  }
  alert(`${key}: ${localStorage.getItem(key)}`);
}

أو نحصل على المفاتيح الخاصة بالكائن باستخدام الأمر Object.keys، ثم نطبق الحلقة عليها:

let keys = Object.keys(localStorage);
for(let key of keys) {
  alert(`${key}: ${localStorage.getItem(key)}`);
}

ستنجح الطريقة الأخيرة لأنّ التابع Object.keys سيعيد المفاتيح التي ترتبط بالكائن فقط، ويتجاهل النموذج الأولي prototype.

قيم نصية فقط

يجب الانتباه إلى أنّ المفاتيح وقيمها كائنات نصية، وستحوّل أي أنواع أخرى - مثل الأرقام- إلى قيم نصية تلقائيًا:

sessionStorage.user = {name: "John"};
alert(sessionStorage.user); // [object Object]

يمكن استخدام JSON لتخزين الكائنات أيضًا:

sessionStorage.user = JSON.stringify({name: "John"});

// لاحقًا
let user = JSON.parse( sessionStorage.user );
alert( user.name ); // John

كما يمكن تحويل كائن التخزين بالكامل إلى نص، لأغراض التنقيح مثلًا:

// لتبدو النتيجة أفضل JSON.stringify يمكن إضافة خيارات تنسيق إلى
alert( JSON.stringify(localStorage, null, 2) );

الكائن sessionStorage

يُستخدم في حالات أقل من الكائن localStorage، وله نفس التوابع والخصائص، لكنها أكثر محدوديةً.

  • يتواجد الكائن فقط ضمن النافذة الحالية المفتوحة ضمن المتصفح.
  • سيكون لنافذة أخرى مفتوحة ضمن المتصفح كائن آخر خاص بها.
  • تتشارك النوافذ الضمنية الموجودة في نافذة نفس الكائن، بفرض أنها مشتركة بالأصل.
  • تبقى البيانات المخزنة بعد تحديث الصفحة، لكنها تُحذف عند إغلاق النافذة وإعادة فتحها.

شغّل هذه الشيفرة لترى آلية عمل الكائن:

sessionStorage.setItem('test', 1);

ثم حدّث الصفحة. عندها ستلاحظ أن البيانات مازالت موجودة:

alert( sessionStorage.getItem('test') ); // after refresh: 1

لكن لو فتحت الصفحة نفسها في نافذة أخرى، وحاولت تنفيذ الشيفرة السابقة مجددًا، فستعيد القيمة "null" أي أنها لم تجد شيئًا، لأنّ الكائن sessionStorage لا يتعلق فقط بالأصل المشترك بل بالنافذة المفتوحة في المتصفح، لذا يندر استخدامه.

أحداث التخزين

عندما تُحدّث البيانات ضمن كائني التخزين فستقع أحداث التخزين التالية:

  • key: المفتاح الذي تغيّر، وسيعيد null إذا استدعي التابع ()clear.
  • oldValue: القيمة القديمة، وستكون null إذا أضيف المفتاح حديثًا.
  • newValue: القيمة الجديدة، وستكون null إذا حُذف المفتاح.
  • url: عنوان الصفحة التي حدث فيها التغيير.
  • storageArea أو أحد الكائنين localStorage أو sessionStorage حيث حدث التغيير.

أمّا الأمر الهام فهو أنّ هذه الأحداث ستقع في كل الكائنات window التي يمكن فيها الوصول إلى كائن تخزين البيانات، عدا تلك التي سببت وقوع الحدث.

تخيل وجود نافذتين في المتصفح تعرضان الصفحة نفسها، عندئذ ستتشارك النافذتان الكائن localStorage نفسه، وقد يكون عرض الصفحة في نافذتين مختلفتين مناسبًا لاختبار الشيفرة التي سنعرضها تاليًا، إذا استمعت كلتا النافذتين إلى الحدث window.onstorage، فستتفاعلان مع التحديثات التي تجري في كلٍّ منهما.

// يقع عند تحديث كائن التخزين من قبل صفحة أخرى
window.onstorage = event => { // same as window.addEventListener('storage', event => {
  if (event.key != 'now') return;
  alert(event.key + ':' + event.newValue + " at " + event.url);
};

localStorage.setItem('now', Date.now());

لاحظ أنّ الحدث سيتضمن أيضًا event.url، وهو عنوان الصفحة التي حصل فيها التغيير، كما يتضمن الحدث كائن التخزين (سيبقى الحدث نفسه للكائنين sessionStorage وlocalStorage)، لذا سيشير الحدث event.storageArea إلى الكائن الذي جرى تعديله منهما، وبما أننا نريد أن نعيد ضبط قيمة ما استجابةً للتغيير، فسيسمح ذلك للنوافذ ذات الأصل المشترك بتبادل الرسائل.

تدعم المتصفحات الحديثة الواجهة البرمجية لقناة البث Broadcast channel API، وهي واجهة خاصة بتبادل الرسائل بين النوافذ التي لها أصل مشترك. لهذه الواجهة ميزات متكاملة أكثر لكنها أقل دعمًا، ومع ذلك فستجد الكثير من المكتبات التي توائم هذه الواجهة مع المتصفحات بالاستفادة من الكائن localStorage مما يجعلها متاحةً في أي مكان.

خلاصة

يسمح الكائنان localStorage وsessionStorage بتخزين الأزواج (مفتاح/قيمة) في المتصفح.

  • المفتاح key والقيمة value من النوع النصي.
  • الحد الأقصى للتخزين بحدود 5 ميغابايت وذلك تبعًا للمتصفح.
  • ليس لها فترة صلاحية.
  • ترتبط البيانات بأصل الصفحة (نطاق/ منفذ/بروتوكول).
localStorage sessionStorage
مشترك بين النوافذ التي لها نفس الأصل تُرى ضمن نافذة واحدة في المتصفح بما فيها النوافذ الضمنية التي لها نفس الأصل
تبقى بعد إعادة تشغيل المتصفح تبقى بعد تحديث الصفحة لكنها تحذف عند إغلاق النافذة

الواجهة البرمجية:

  • (setItem(key, value: يخزّن أزواج (مفتاح/قيمة).
  • (getItem(key: يحصل على القيمة عن طريق المفتاح.
  • (removeItem(key: يزيل المفتاح مع قيمته.
  • ()clear: يحذف كل شيء.
  • (key(index: يعيد مفتاحًا ذا موقع محدد.
  • length: يعطي عدد العناصر المخزّنة.
  • Object.keys: للحصول على جميع المفاتيح.
  • يمكن الوصول إلى المفاتيح عبر خصائص الكائن، لكن لن يقع الحدث storage في هذه الحالة.

أحداث التخزين:

  • تقع نتيجةً للاستدعاءات التالية setItem أو removeItem أو clear.
  • تحتوي على كل البيانات المتعلقة بالعملية key/oldValue/newValue، وبالصفحة url، وبكائن التخزين storageArea.
  • تقع في جميع النوافذ التي يمكنها الوصول إلى كائن التخزين، عدا تلك التي ولّدته، ضمن نافذة واحدة بالنسبة للكائن sessionStorage، ولكل النوافذ بالنسبة للكائن localStorage.

مهمات لإنجازها

الحفظ التلقائي لحقل من حقول نموذج

أنشئ حقلًا نصيًا textarea يحفظ تلقائيًا القيمة التي يحتويها بعد كل تغيير فيها، وبالتالي عندما يُغلق المستخدم الصفحة عن طريق الخطأ، ثم يفتحها مجددًا، فسيجد النص الذي لم يكمله بعد في مكانه.

افتح المثال في بيئة تجريبية. وإن أردت الحل، فهو في هذه البيئة التجريبية.

ترجمة -وبتصرف- للفصل localStorage, sessionStorage من سلسلة The Modern JavaScript Tutorial.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...