البرمجة مبتدئ 7 دقيقة

كيفية حفظ البيانات في localStorage بأمان

localStorage أبسط طريقة لحفظ البيانات عبر تحديثات الصفحة في المتصفح — بلا سيرفر وبلا قاعدة بيانات. يخزن أزواج مفتاح-قيمة كنصوص، ويصمد بعد إغلاق التبويب وإعادة تشغيل المتصفح، وهو متزامن في القراءة والكتابة.

لكن الاستخدام غير الحذر ينتهي بأعطال متوقعة: استدعاء JSON.parse على مفتاح مفقود، والوصول إلى حد 5 ميجابايت بصمت، أو تخزين بيانات حساسة يمكن لأي JavaScript على الصفحة قراءتها. يغطي هذا الدليل دوال مساعدة آمنة للقراءة والكتابة، وحالات الحافة التي تسبب الأعطال، وما يجب ألا تخزنه أبداً، وكيفية التفاعل مع التغييرات عبر تبويبات المتصفح.

الخطوات

  1. 1

    افهم أساسيات الـ API

    يخزن localStorage كل شيء كنص. لحفظ الكائنات أو المصفوفات يجب تسلسلها بـJSON.stringify عند الكتابة وفكّ تسلسلها بـJSON.parse عند القراءة. القيم التي لا يمكن تسلسلها (الدوال، undefined، المراجع الدائرية) تُحذف أو تُحوَّل بصمت.

    javascript
    // Write
    localStorage.setItem('theme', 'dark');
    localStorage.setItem('user', JSON.stringify({ name: 'Alice', age: 30 }));
    
    // Read
    const theme = localStorage.getItem('theme');         // 'dark' or null
    const user  = JSON.parse(localStorage.getItem('user')); // object or null
    
    // Delete one key
    localStorage.removeItem('theme');
    
    // Wipe everything (careful in production)
    localStorage.clear();
  2. 2

    اعرف متى يُعطب JSON.parse

    JSON.parse(null) يُعيد null بأمان — يُعيد getItem القيمة null للمفاتيح المفقودة، وهي تُحوَّل إلى النص "null" الذي يُحلَّل بلا مشكلة. لكن JSON.parse(undefined) يُطلق SyntaxError، وكذلك تحليل نص تالف. لا تقرأ أبداً بدون حماية.

    javascript
    // Safe — null is valid input for JSON.parse
    JSON.parse(null);        // → null
    JSON.parse('null');      // → null
    
    // These crash:
    JSON.parse(undefined);   // SyntaxError: Unexpected token u
    JSON.parse('');          // SyntaxError: Unexpected end of JSON input
    JSON.parse('{bad json'); // SyntaxError
  3. 3

    اكتب دالة getItem آمنة

    غلّف عمليات القراءة في try/catch وقدّم قيمة افتراضية. يعالج هذا المفاتيح المفقودة، والبيانات التالفة، والحالة النادرة حين يكون localStorage غير متاح (التصفح الخاص في بعض المتصفحات، أو سياسات الأمان).

    javascript
    function getItem(key, defaultValue = null) {
      try {
        const raw = localStorage.getItem(key);
        if (raw === null) return defaultValue;
        return JSON.parse(raw);
      } catch {
        return defaultValue;
      }
    }
    
    // Usage
    const prefs = getItem('user-prefs', { theme: 'light', lang: 'en' });
    const count = getItem('visit-count', 0);
  4. 4

    اكتب دالة setItem آمنة

    يُطلق localStorage.setItem خطأ QuotaExceededError عند الوصول إلى حد 5 ميجابايت. بدون حماية، سيُعطب تطبيقك بصمت. غلّف في try/catch — على الأقل سجّل الفشل حتى تعرف أنه حدث.

    javascript
    function setItem(key, value) {
      try {
        localStorage.setItem(key, JSON.stringify(value));
      } catch (err) {
        if (err.name === 'QuotaExceededError') {
          console.warn('localStorage quota exceeded. Consider evicting old data.');
          // Optional: remove the oldest entry and retry
        } else {
          console.error('localStorage write failed:', err);
        }
      }
    }
    
    // Usage
    setItem('user-prefs', { theme: 'dark', lang: 'ar' });
    setItem('cart', [{ id: 1, qty: 2 }, { id: 5, qty: 1 }]);
  5. 5

    احترم حد 5 ميجابايت

    الحد مشترك لكل الـ origin وعبر جميع المفاتيح. تُستنفد الـ JSON الكبيرة (الصور مشفرة كـ base64، ردود الـ API الكاملة) بسرعة. خزّن فقط ما تحتاجه عبر الجلسات — معرّفات، وتفضيلات، ونصوص قصيرة. للتخزين الأكبر في وضع عدم الاتصال، استخدم IndexedDB.

    javascript
    // Rough size check before writing
    function estimatedSize(value) {
      return new Blob([JSON.stringify(value)]).size; // Bytes
    }
    
    // Check current total usage
    function storageUsed() {
      let total = 0;
      for (let i = 0; i < localStorage.length; i++) {
        const key = localStorage.key(i);
        total += (key.length + (localStorage.getItem(key) ?? '').length) * 2; // UTF-16
      }
      return total;
    }
    
    console.log(`Storage used: ${(storageUsed() / 1024).toFixed(1)} KB`);
  6. 6

    لا تخزن الأسرار أو الرموز المميزة

    localStorage متاح لأي JavaScript تعمل على الصفحة — بما فيها السكريبتات الخارجية، والإعلانات، وحقن XSS. لا تخزن أبداً رموز المصادقة، وجلسات cookies، والكلمات السرية، ومفاتيح الـ API، أو البيانات الشخصية فيه. استخدم cookies نوع HttpOnly لرموز المصادقة بدلاً من ذلك؛ يُرسلها المتصفح تلقائياً ولا تستطيع JavaScript قراءتها.

  7. 7

    استمع للتغييرات عبر التبويبات بحدث storage

    يُطلق حدث storage في كل تبويب مفتوح آخر حين يتغير مفتاح — لا في التبويب الذي أجرى التغيير. استخدمه لمزامنة الحالة (الثيم، المصادقة، السلة) عبر التبويبات بدون استطلاع.

    javascript
    window.addEventListener('storage', (event) => {
      // event.key       — the key that changed (null if clear() was called)
      // event.oldValue  — previous value (string)
      // event.newValue  — new value (string, or null if removed)
      // event.storageArea — the storage object that changed
      // event.url       — URL of the page that made the change
    
      if (event.key === 'theme') {
        const theme = JSON.parse(event.newValue ?? 'null') ?? 'light';
        document.documentElement.setAttribute('data-theme', theme);
        console.log(`Theme synced from another tab: ${theme}`);
      }
    
      if (event.key === null) {
        // localStorage.clear() was called — reset all state
        resetAppState();
      }
    });

نصائح ومحاذير

  • بادئ مفاتيحك بمساحة اسم التطبيق (مثل <code>myapp:theme</code>) لتجنب التعارض مع سكريبتات خارجية أو مشاريع أخرى على نفس الـ origin.
  • <code>sessionStorage</code> له نفس الـ API لكنه يُمسح عند إغلاق التبويب — استخدمه للبيانات التي لا يجب أن تستمر بعد الجلسة.
  • نسّخ مخطط بياناتك المخزنة دائماً. أضف حقل <code>_version</code> وهاجر أو امسح عند عدم التطابق بدلاً من الانهيار على بنيانات غير متوقعة.
  • في اختبارات الوحدة، استبدل <code>localStorage</code> بكائن بسيط أو بحزمة <code>jest-localstorage-mock</code> — لا تعتمد على التخزين الحقيقي في الاختبارات.
  • لا يُطلق حدث <code>storage</code> في نفس التبويب الذي أجرى التغيير — فقط في التبويبات الأخرى. لا تستخدمه لمزامنة داخل نفس التبويب.

خاتمة

localStorage مفيد تحديداً لبساطته — لكن هذه البساطة تخفي بعض أنماط الفشل الحقيقية. استخدم الدوال المساعدة الآمنة getItem/setItem، وأبقِ البيانات صغيرة وغير حساسة، وستحصل على طبقة حفظ موثوقة على جانب العميل بلا اعتماديات وبلا إعداد.

#JavaScript #Storage #Browser
العودة إلى جميع الأدلة

هل تحتاج مساعدة في مشروعك؟

احجز استشارة مجانية لمدة 30 دقيقة لمناقشة تحدياتك التقنية واستكشاف الحلول معًا.