الوحدة 4 · JavaScript في المتصفّح
حفظ البيانات: JSON وlocalStorage
16 دقراءة 4 دبيئة تجريب الكود
الدرس في لمحة
تحوّل البيانات إلى نص JSON وبالعكس باستخدام JSON.stringify وJSON.parse، وتعرف ما لا يستطيع JSON حمله، وتحفظ مصاريف Pocket وتحمّلها من localStorage بأمان.
لا فيديو هنا، بل الشيء الحقيقي: درس مكتوب وأمثلة قابلة للتشغيل واختبار وتدريب.
أضف ثلاثة مصاريف إلى Pocket، ثم أعِد تحميل الصفحة، وستجدها اختفت. كل ما بنيته يعيش في متغيّرات JavaScript، والمتغيّرات لا تعيش إلا ما دامت الصفحة. ولكي تتذكّر البيانات بين الزيارات، تحتاج إلى تخزينها في مكان ما، وأبسط مكان تستطيع صفحة ويب أن تخزّن فيه البيانات هو متصفّح المستخدم نفسه. لكن أولًا، يجب أن تتحوّل البيانات إلى نص.
JSON: البيانات على شكل نص
JSON (اختصار JavaScript Object Notation) صيغة نصية للبيانات. يكاد شكلها يطابق صيغة كتابة الكائنات والمصفوفات في JavaScript، وهذا ليس مصادفة، وهي اليوم الصيغة الأكثر شيوعًا لإرسال البيانات بين البرامج على الويب.
const expenses = [
{ id: "exp-1", label: "Coffee", amount: 450, tags: ["work"] },
{ id: "exp-2", label: "Train", amount: 1220, tags: [] },
];
const text = JSON.stringify(expenses);
console.log(typeof text);
console.log(text);
const back = JSON.parse(text);
console.log(back[1].label, back === expenses);JSON.stringify تحوّل قيمة إلى نص JSON. وJSON.parse تقرأ نص JSON وتبني منه قيمًا جديدة. والنتيجة نسخة جديدة بالمحتويات نفسها، ولهذا تكون back === expenses تساوي false.
قواعد JSON أكثر صرامة من قواعد JavaScript: أسماء الخصائص يجب أن تكون بين علامات تنصيص مزدوجة، والنصوص لا تستخدم إلا العلامات المزدوجة، والفواصل الزائدة في النهاية غير مسموحة. نادرًا ما تكتب JSON يدويًا، لكن حين تفعل، تسبّب هذه القواعد الثلاث معظم أخطاء التحليل. وللحصول على نسخة مقروءة، تُزيح JSON.stringify(value, null, 2) المخرجات بمسافتين.
ما لا يستطيع JSON حمله
يعرف JSON النصوص والأرقام والقيم المنطقية وnull والمصفوفات والكائنات البسيطة. وكل ما عدا ذلك يتغيّر أو يضيع في الطريق:
const tricky = {
when: new Date("2026-10-05T09:30:00Z"),
note: undefined,
format() { return "hi"; },
tags: new Set(["work"]),
ratio: NaN,
};
console.log(JSON.stringify(tricky));التواريخ تصبح نصوصًا بصيغة ISO ولا تعود تواريخ، وخصائص undefined والدوال تُسقط، والـ Sets والـ Maps تصبح كائنات فارغة، وNaN تصبح null. ويتجنّب Pocket كل هذا بالتصميم: المبالغ أعداد صحيحة، والتواريخ تُخزَّن نصوصًا مثل "2026-10-05"، والفئات نصوص بسيطة. واختيار بيانات تناسب JSON منذ البداية يوفّر كثيرًا من كود التحويل لاحقًا.
localStorage: مخزن صغير في المتصفّح
يحصل كل موقع على مخزن صغير للمفاتيح والقيم في متصفّح كل مستخدم، اسمه localStorage. إنه ينجو من إعادة التحميل ومن إعادة تشغيل المتصفّح، ولا يحمل إلا النصوص:
localStorage.setItem("pocket.theme", "dark");
console.log(localStorage.getItem("pocket.theme")); // "dark"
console.log(localStorage.getItem("nothing-here")); // null
localStorage.removeItem("pocket.theme");لتخزين مصفوفة مصاريف، حوّلها إلى JSON عند الإدخال وحلّلها عند الإخراج:
const STORAGE_KEY = "pocket.expenses.v1";
function saveExpenses(expenses) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(expenses));
}
function loadExpenses() {
try {
const text = localStorage.getItem(STORAGE_KEY);
const data = text === null ? [] : JSON.parse(text);
return Array.isArray(data) ? data : [];
} catch {
return [];
}
}loadExpenses دفاعية عن قصد، لأنها تُنفَّذ مع كل تحميل للصفحة ويجب ألّا تُسقط التطبيق أبدًا. الزائر لأول مرة لم يحفظ شيئًا، فتُرجع getItem القيمة null ونبدأ بقائمة فارغة. والنص التالف يجعل JSON.parse ترمي خطأ، فتلجأ catch إلى قائمة فارغة. وبعض أوضاع الخصوصية في المتصفّحات تمنع التخزين كليًا، وهذا يرمي خطأ أيضًا يذهب إلى catch نفسها. وأخيرًا، تحمي Array.isArray من JSON صالح لكن بشكل خاطئ. والجزء .v1 في المفتاح يتيح لإصدار مستقبلي من Pocket أن يغيّر صيغة البيانات وينقل البيانات القديمة بدلًا من أن يسيء قراءتها.
ثم اربط كل ذلك: حمّل مرة واحدة عند البدء، واحفظ في كل مرة تتغيّر فيها البيانات، في المكان نفسه الذي تستدعي فيه renderExpenses أصلًا:
let expenses = loadExpenses();
renderExpenses();
function setExpenses(next) {
expenses = next;
saveExpenses(expenses);
renderExpenses();
}تمرير كل تغيير عبر دالة واحدة setExpenses يعني أنك لا تستطيع أن تنسى الحفظ في معالج حدث وتتذكّره في آخر.
لترى ما هو مخزّن، افتح DevTools، واذهب إلى لوحة Application في Chrome أو Edge (أو Storage في Firefox)، ووسّع Local Storage. يمكنك هناك قراءة المدخلات وتعديلها وحذفها، وهذه أسرع طريقة لاختبار ما يفعله تطبيقك مع البيانات المفقودة أو التالفة. وهناك أيضًا sessionStorage، بالدوال نفسها، وتُمسح حين يُغلق التبويب.
صار بإمكانك الاحتفاظ بالبيانات بين الزيارات. والمصدر الكبير الآخر للبيانات هو خادم على الإنترنت، والتحدّث إليه يعني انتظار الإجابة. والانتظار هو موضوع الدرس التالي.
أهم النقاط
- JSON صيغة نصية للبيانات؛
JSON.stringifyتحوّل القيم إلى نص JSON، وJSON.parseتعيد نص JSON إلى قيم. - يحمل JSON النصوص والأرقام والقيم المنطقية وnull والمصفوفات والكائنات البسيطة؛ أما الدوال و
undefinedفتُسقط، والتواريخ تصبح نصوصًا. localStorageلا تخزّن إلا النصوص، لكل موقع على حدة، في متصفّح المستخدم؛ احفظ باستخدامsetItem(key, JSON.stringify(data)).- يجب أن ينجو التحميل من غياب المفتاح (
getItemتُرجعnull) ومن النص التالف (JSON.parseترمي خطأ)، لذلك ضعه داخلtry/catchمع قيمة بديلة.