توسعه فرانت‌اند

localStorage, sessionStorage و IndexedDB

عمر و ظرفیت واقعی داده، محدودیت امنیت و تراکنش، expiry و نسخه؛ نمونه کامل ترجیح غیرحساس با خطا و پاک‌سازی محدود.

1404/11/20 3 دقیقه مطالعه 1,557 بازدید

یک یادداشت کوتاه پس از reload برمی‌گردد و کاربر خیال می‌کند روی حسابش ذخیره شده است. در واقع داده فقط در مرورگر همان دستگاه مانده و ممکن است با پاک کردن اطلاعات سایت حذف شود. انتخاب storage سمت client باید با عمر، حساسیت و نقش داده هماهنگ باشد. رابط هم باید معلوم کند ذخیره محلی با ثبت روی سرور یا همگام‌سازی میان دستگاه‌ها متفاوت است.

localStorage، sessionStorage و IndexedDB ابزارهای مهم‌اند، اما تنها شکل‌های ذخیره‌سازی وب نیستند. Cookie، Cache API و ابزارهای دیگر قراردادهای متفاوتی دارند. برای انتخاب، ابتدا مسئله را بنویسید: ترجیح نمایش کوچک، پیش‌نویس موقت، فهرست داده ساختاریافته یا پاسخ عمومی شبکه. حجم فقط یکی از معیارهاست؛ مدل داده، الگوی دسترسی و امکان از دست رفتن هم اثر دارند.

Web Storage ساده و synchronous است

مرجع Web Storage در MDN ذخیره کلید و مقدار و رفتار synchronous آن را توضیح می‌دهد. مقدارها string هستند؛ برای object معمولاً تبدیل و parse لازم است. نوشتن داده بزرگ یا انجام مکرر عملیات می‌تواند جریان اصلی را درگیر کند. سادگی API دلیل مناسب بودن آن برای مجموعه بزرگ یا عملیات پرشمار نیست. دامنه داده را محدود و هزینه تبدیل را در نظر بگیرید.

localStorage بر اساس origin جدا می‌شود و برای داده‌هایی که قرار است میان مراجعه‌ها باقی بمانند قابل استفاده است. این ماندگاری تضمین همیشگی نیست. تغییر protocol، host یا port می‌تواند به ناحیه متفاوتی اشاره کند. داده یک محیط توسعه لزوماً در محیط دیگر وجود ندارد. نبودن مقدار باید حالت عادی قابل مدیریت باشد، نه خطایی که تمام صفحه را از کار می‌اندازد.

sessionStorage عمر صفحه‌نشست دارد

مستندات sessionStorage تقسیم‌بندی origin و تب و حفظ داده در reload همان نشست را توضیح می‌دهد. پایان نشست تب با مقدار محلی ماندگار تفاوت دارد و بعضی شرایط opener می‌توانند نسخه اولیه‌ای جدا بسازند. آن را یک مخزن مشترک ساده میان همه تب‌ها فرض نکنید. نام session در این API با همه مفهوم‌های نشست سرور یا cookie معادل نیست.

برای مرحله موقت یک فرم، sessionStorage می‌تواند مفید باشد، ولی مخاطب باید بداند ادامه روی دستگاه دیگر وجود ندارد. بازیابی خودکار اطلاعات قدیمی نیز ممکن است انتخاب نادرست بسازد؛ تاریخ و نسخه داده اهمیت دارد. اگر اطلاعات خصوصی وارد فرم می‌شود، ضرورت نگهداری و مسیر پاک کردن را بررسی کنید. کوتاه بودن عمر داده به معنی امن بودن آن در برابر کد اجراشده داخل همان origin نیست.

IndexedDB مخزن نامحدود نیست

مرجع IndexedDB مدل object store، index و تراکنش‌ها را توضیح می‌دهد. دسترسی آن async است و برای داده ساختاریافته و الگوهای جستجوی مناسب کاربرد دارد. این قابلیت به معنی پایگاه داده سرور یا ظرفیت بی‌نهایت نیست. Schema، نسخه و مهاجرت باید تعریف شوند و رفتار شکست تراکنش به نتیجه برنامه مربوط شود.

انتخاب index از queryهای واقعی می‌آید. اگر داده را فقط به عنوان یک JSON بزرگ داخل یک رکورد قرار دهید، مزیت جستجوی ساختاریافته را شاید استفاده نکنید. در ارتقای نسخه، صفحه‌های قدیمی باز می‌توانند بر جریان تغییر اثر داشته باشند. قرارداد migration و بسته شدن ارتباط باید مشخص باشد. Library کمکی می‌تواند کار را ساده کند، ولی مسئولیت مدل داده و سازگاری نسخه را حذف نمی‌کند.

ظرفیت و حذف به مرورگر وابسته‌اند

راهنمای quota و eviction تفاوت سیاست‌ها و خطای پر شدن ظرفیت را شرح می‌دهد. عدد ثابت پنج تا ده مگابایت نباید به عنوان قرارداد مشترک همه APIها و همه مرورگرها نوشته شود. فضای بیشتر IndexedDB هم نامحدود نیست. نوع ذخیره، شرایط دستگاه و سیاست محیط بر ظرفیت و نگهداری اثر دارند.

درخواست persistence، اگر محیط آن را فراهم کند، تضمین عدم حذف توسط خود کاربر نیست. حالت خصوصی و تنظیمات مرورگر هم می‌توانند رفتار متفاوتی داشته باشند. برنامه باید خواندن و نوشتن ناموفق را مدیریت کند و به داده محلی به عنوان تنها نسخه اطلاعات ضروری وابسته نشود. برای عملیات آفلاین، وضعیت «روی دستگاه مانده» با «روی سرور ثبت شد» جدا باشد تا کاربر درباره نتیجه تصور نادرست نگیرد.

نمونه کامل ترجیح غیرحساس

فایل بعدی را به نام preference.html ذخیره کنید. همه HTML، CSS و JavaScript در همان فایل‌اند و تصویر یا API خارجی وجود ندارد. نمونه فقط انتخاب تراکم نمایش را با localStorage نگه می‌دارد؛ رمز، داده حساب یا متن خصوصی دریافت نمی‌کند. برای رفتار origin روشن، از localhost سرو کنید: در پوشه تازه npm init -y و npm install -D http-server، سپس npx http-server . -p 8080.

صفحه را از http://localhost:8080/preference.html باز کنید. مقدار ذخیره‌شده نسخه و موعد انقضای هفت‌روزه دارد؛ هفت روز یک انتخاب آموزشی است. دکمه بازخوانی از storage می‌خواند و دکمه حذف فقط کلید همین نمونه را پاک می‌کند. کد همه داده‌های origin را clear نمی‌کند. در خطای خواندن یا نوشتن، پیام وضعیت نشان داده می‌شود و کنترل‌های صفحه قابل استفاده می‌مانند.

<!doctype html>
<html lang="fa" dir="rtl"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ترجیح تراکم نمایش</title><style>body{font-family:Tahoma,Arial,sans-serif;line-height:1.9;margin:0;color:#17243b;background:#f2f4f7}main{max-width:36rem;margin:auto;padding:2rem 1rem}button,select{font:inherit;padding:.65rem;margin:.4rem;border:1px solid #66758b;border-radius:.4rem;background:white;color:#17243b}:focus-visible{outline:3px solid #946000;outline-offset:3px}</style></head><body><main><h1>ترجیح غیرحساس نمایش</h1><label for="density">تراکم</label><select id="density"><option value="comfortable">با فاصله بیشتر</option><option value="compact">فشرده</option></select><div><button id="save" type="button">ذخیره محلی</button><button id="load" type="button">بازخوانی</button><button id="remove" type="button">حذف ترجیح نمونه</button></div><p id="status" role="status"></p></main>
<script>
const KEY='tara-density-demo-v1';const WEEK=7*24*60*60*1000;const density=document.getElementById('density');const status=document.getElementById('status');const allowed=['comfortable','compact'];
function load(){try{const raw=localStorage.getItem(KEY);if(raw===null){density.value='comfortable';status.textContent='ترجیحی ذخیره نشده است.';return;}const value=JSON.parse(raw);if(!value||value.version!==1||!allowed.includes(value.density)||typeof value.expiresAt!=='number'||!Number.isFinite(value.expiresAt)||value.expiresAt<=Date.now()){localStorage.removeItem(KEY);density.value='comfortable';status.textContent='ترجیح قدیمی یا نامعتبر کنار گذاشته شد.';return;}density.value=value.density;status.textContent='ترجیح معتبر همین دستگاه بازخوانی شد.';}catch{status.textContent='خواندن ترجیح محلی انجام نشد.';}}
document.getElementById('save').addEventListener('click',()=>{try{if(!allowed.includes(density.value))return;localStorage.setItem(KEY,JSON.stringify({version:1,density:density.value,expiresAt:Date.now()+WEEK}));status.textContent='ترجیح برای هفت روز روی همین دستگاه ذخیره شد.';}catch{status.textContent='ذخیره محلی انجام نشد؛ این انتخاب روی سرور ثبت نشده است.';}});
document.getElementById('load').addEventListener('click',load);
document.getElementById('remove').addEventListener('click',()=>{try{localStorage.removeItem(KEY);density.value='comfortable';status.textContent='کلید همین نمونه حذف شد.';}catch{status.textContent='حذف ترجیح انجام نشد.';}});load();
</script></body></html>

بارگذاری مقدار با بررسی نوع، نسخه و موعد انجام می‌شود. داده JSON معتبر اما دارای شکل نامناسب هم به عنوان ترجیح پذیرفته نمی‌شود. نمایش وضعیت با textContent است و رشته ذخیره‌شده وارد HTML خام نمی‌شود. کنترل فقط مقدارهای شناخته‌شده comfortable و compact را می‌پذیرد. اگر ذخیره شکست خورد، صفحه نباید موفقیت محلی را گزارش کند. این نمونه همگام‌سازی تب‌ها یا سرور را پیاده نکرده است.

Expiry و نسخه دو مسئولیت متفاوت‌اند

localStorage زمان انقضای داخلی مشابه بعضی cookieها ندارد؛ اگر نیاز دارید، برنامه باید آن را تعریف و هنگام مصرف بررسی کند. گذاشتن expiresAt در object به‌تنهایی داده را خودکار حذف نمی‌کند. برای پاک‌سازی، مسیر خواندن یا یک قرارداد مشخص لازم است. تغییر ساعت دستگاه نیز می‌تواند بر تفسیر زمان اثر داشته باشد؛ برای داده با تعهد مهم، ساعت محلی تنها منبع قابل اعتماد نیست.

نسخه، شکل داده را توضیح می‌دهد. بعد از تغییر schema، مقدار قدیمی باید مهاجرت یا کنار گذاشته شود. حدس زدن از وجود یک فیلد ممکن است خطای خاموش بسازد. دامنه migration را محدود کنید و داده نامعتبر را بی‌دلیل به ساختار تازه تبدیل نکنید. برای اطلاعات مهم، مسیر بازیابی و گزارش لازم است؛ مثال ترجیح نمایش می‌تواند ساده‌تر باشد چون از دست رفتن آن نتیجه حساس ایجاد نمی‌کند.

ذخیره محلی مرز امنیتی حساب نیست

کدی که در زمینه origin اجرا می‌شود ممکن است به storage مربوط دسترسی داشته باشد. رمزنگاری با کلیدی که همان کد به آن دسترسی دارد، در برابر اجرای مخرب در همان زمینه تضمین حفاظت نمی‌دهد. انتخاب محل token و داده خصوصی باید در معماری امنیتی پروژه بررسی شود. نسخه محلی را صرفاً به دلیل اینکه در پایگاه داده سرور نیست بی‌خطر معرفی نکنید.

برای دستگاه مشترک، خروج از حساب و تغییر کاربر باید اثر داده باقی‌مانده را مدیریت کند. حذف فقط UI کافی نیست اگر داده قابل بازخوانی مانده باشد. namespace کلیدها می‌تواند تداخل را کم کند، اما جای مجوز نیست. اطلاعات حساس را در نام کلید، URL یا پیام log هم بی‌دلیل ثبت نکنید. سیاست نگهداری باید بر نیاز واقعی تکیه کند و راه حذف قابل فهم باشد.

تب‌ها و عملیات همزمان

دو تب می‌توانند ترجیح یا داده را تغییر دهند. قرارداد storage event و به‌روزرسانی UI باید معلوم باشد؛ همان صفحه‌ای که تغییر می‌دهد هم رفتار مستقل دارد. الگوی خواندن، تغییر و نوشتن ساده ممکن است در رقابت چند مصرف‌کننده نتیجه‌ای ناخواسته بسازد. اگر داده به تراکنش نیاز دارد، ابزار و مدل مناسب را انتخاب کنید. localStorage را مخزن عمومی عملیات اتمیک چندمرحله‌ای فرض نکنید.

در IndexedDB، تراکنش برای مجموعه مشخصی از storeها و mode تعریف می‌شود. رفتار async باید با عمر همان تراکنش هماهنگ باشد. پاسخ یک درخواست داخل آن با نهایی شدن کل تراکنش یکسان نیست. این فرق در اعلام موفقیت اهمیت دارد. راهنمای اعلان‌ها درباره رابطه پیام با نتیجه واقعی توضیح می‌دهد. API storage نتیجه را فراهم می‌کند، اما زمان گفتن «ذخیره شد» را برنامه تعیین می‌کند.

انتخاب و تحویل

برای ترجیح کوچک، Web Storage می‌تواند کافی باشد؛ برای داده ساختاریافته و query، IndexedDB گزینه مناسب‌تری ممکن است باشد؛ برای پاسخ عمومی شبکه، Cache API قرارداد دیگری دارد. نمونه PWA دامنه cache عمومی را نشان می‌دهد. هیچ‌کدام جای نسخه معتبر سرور را در همه کارها نمی‌گیرند. مسئله را با نوع و عمر داده تعریف کنید.

در تحویل، نام کلید یا store، نسخه، سیاست انقضا، حالت خطا و رفتار حذف را ثبت کنید. درباره ذخیره روی دستگاه و همگام‌سازی توضیح روشن بدهید. برای UI قابل مشاهده، بازخوردهای کوچک به هماهنگی عمل و نتیجه کمک می‌کنند. انتخاب storage زمانی پایدار است که دامنه و محدودیت آن معلوم باشد و برنامه از نبودن داده یا شکست نوشتن غافلگیر نشود.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!