Web Development

Web Storage API: localStorage, sessionStorage, IndexedDB

Kuasai semua cara menyimpan data di browser — localStorage, sessionStorage, IndexedDB, Cookies, dan pola penyimpanan offline

1. Pengenalan Web Storage

Browser modern menyediakan beberapa mekanisme untuk menyimpan data di sisi client (di dalam browser pengguna). Ini sangat berguna untuk menyimpan preferensi user, data cache, state aplikasi, dan bahkan data offline.

Perbandingan Semua Storage

Fitur localStorage sessionStorage IndexedDB Cookies
Kapasitas~5-10 MB~5-10 MBRatusan MB+~4 KB
Tipe DataStringStringBebas (object)String
PersistenceSelamanyaPer tab sessionSelamanyaBisa expiry
Dikirim ke ServerTidakTidakTidakYa (setiap request)
Async?SinkronSinkronAsyncSinkron
Complex Query
Web Workers
Diagram: Kapan Menggunakan Storage Apa?
  Perlu menyimpan data di browser?
        │
        ├── Data kecil (preferences, settings)?
        │   └── localStorage
        │
        ├── Data sementara (form draft)?
        │   └── sessionStorage
        │
        ├── Data besar/terstruktur (offline DB)?
        │   └── IndexedDB
        │
        ├── Perlu dikirim ke server?
        │   └── Cookies
        │
        └── Perlu cache HTTP response?
            └── Cache API (via Service Worker)

2. localStorage

localStorage adalah penyimpanan key-value yang sederhana dan persisten. Data disimpan selamanya (sampai dihapus manual) dan tersedia untuk semua tab dari origin yang sama.

API Dasar localStorage

JavaScript — localStorage Dasar
// ===== API Dasar localStorage =====

// Menyimpan data (hanya STRING!)
localStorage.setItem('nama', 'Beebane');
localStorage.setItem('umur', '25');       // ← angka jadi string!
localStorage.setItem('aktif', 'true');    // ← boolean jadi string!

// Membaca data (selalu mengembalikan string atau null)
const nama = localStorage.getItem('nama');    // → 'Beebane'
const umur = localStorage.getItem('umur');    // → '25' (bukan 25!)
const tidakAda = localStorage.getItem('x');  // → null

// Menghapus data
localStorage.removeItem('umur');

// Menghapus SEMUA data
localStorage.clear();

// Menghitung jumlah item
console.log(localStorage.length); // → 1 (setelah hapus 'umur')

// Akses berdasarkan index
const key = localStorage.key(0);  // → 'nama'

// ===== Shortcut: Property Access =====
localStorage.tema = 'dark';          // Sama dengan setItem
console.log(localStorage.tema);     // Sama dengan getItem
delete localStorage.tema;           // Sama dengan removeItem

Menyimpan Object & Array

JavaScript — JSON Storage
// ===== Menyimpan Object/Array (gunakan JSON) =====
// localStorage hanya bisa string → gunakan JSON.stringify/parse

const pengaturan = {
    tema: 'dark',
    bahasa: 'id',
    fontSize: 16,
    notifikasi: true,
    sidebar: {
        collapsed: false,
        width: 280
    }
};

// Simpan
localStorage.setItem('pengaturan', JSON.stringify(pengaturan));

// Baca
const saved = JSON.parse(localStorage.getItem('pengaturan'));
console.log(saved.tema);           // → 'dark'
console.log(saved.sidebar.width);  // → 280

// ===== Menyimpan Array =====
const favorites = ['JavaScript', 'Python', 'Rust'];
localStorage.setItem('favorites', JSON.stringify(favorites));

const fav = JSON.parse(localStorage.getItem('favorites'));
console.log(fav.includes('Python')); // → true

// ===== Wrapper Functions (aman dari error) =====
function setItem(key, value) {
    try {
        localStorage.setItem(key, JSON.stringify(value));
        return true;
    } catch (error) {
        // QuotaExceededError — storage penuh!
        console.error('Gagal menyimpan:', error.message);
        return false;
    }
}

function getItem(key, defaultValue = null) {
    try {
        const item = localStorage.getItem(key);
        return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
        console.error('Gagal membaca:', error.message);
        return defaultValue;
    }
}

function removeItem(key) {
    localStorage.removeItem(key);
}

// Penggunaan:
setItem('user', { nama: 'Beebane', role: 'admin' });
const user = getItem('user', { nama: 'Guest', role: 'guest' });
console.log(user.nama); // → 'Beebane'
⚠️ Keamanan localStorage

JANGAN menyimpan data sensitif di localStorage (token autentikasi, password, data pribadi). localStorage bisa diakses oleh semua JavaScript di halaman — jika ada XSS, data Anda bisa dicuri. Gunakan HttpOnly Cookies untuk data sensitif.

3. sessionStorage

sessionStorage memiliki API yang identik dengan localStorage, tapi datanya hilang saat tab ditutup. Setiap tab memiliki sessionStorage yang terpisah.

JavaScript — sessionStorage
// ===== sessionStorage: API Identik dengan localStorage =====

// Simpan
sessionStorage.setItem('formData', JSON.stringify({
    nama: 'Beebane',
    email: 'bee@example.com',
    pesan: 'Halo dunia!'
}));

// Baca
const form = JSON.parse(sessionStorage.getItem('formData'));
console.log(form.nama); // → 'Beebane'

// Hapus
sessionStorage.removeItem('formData');

// Clear semua
sessionStorage.clear();

// ===== Perbedaan dengan localStorage =====

// 1. Data hilang saat tab ditutup
// localStorage: data tetap ada
// sessionStorage: data hilang

// 2. Terpisah per tab
// localStorage: dibagi semua tab dari origin yang sama
// sessionStorage: setiap tab punya sendiri

// 3. Tidak terpengaruh window.open()
// sessionStorage baru dibuat untuk window.open() dengan '_blank'

// ===== Contoh: Draft Form =====
// Simpan draft otomatis agar tidak hilang saat refresh
const formInput = document.getElementById('message');

// Load draft saat halaman dibuka
const draft = sessionStorage.getItem('draft-message');
if (draft) {
    formInput.value = draft;
}

// Auto-save draft saat user mengetik
formInput.addEventListener('input', () => {
    sessionStorage.setItem('draft-message', formInput.value);
});

// Hapus draft saat form berhasil dikirim
form.addEventListener('submit', () => {
    sessionStorage.removeItem('draft-message');
});

// ===== Contoh: Halaman State =====
// Menyimpan scroll position, tab aktif, dll
function savePageState() {
    const state = {
        scrollY: window.scrollY,
        activeTab: document.querySelector('.tab.active')?.dataset.tab,
        filter: document.getElementById('filter')?.value,
        sort: document.getElementById('sort')?.value
    };
    sessionStorage.setItem('pageState', JSON.stringify(state));
}

function restorePageState() {
    const saved = JSON.parse(sessionStorage.getItem('pageState'));
    if (saved) {
        window.scrollTo(0, saved.scrollY);
        if (saved.activeTab) {
            document.querySelector(`[data-tab="${saved.activeTab}"]`)?.click();
        }
    }
}

// Auto-save sebelum unload
window.addEventListener('beforeunload', savePageState);

// Restore saat halaman dimuat
window.addEventListener('load', restorePageState);

4. Storage Events

Event storage di-trigger di tab lain (bukan tab yang mengubah) saat localStorage berubah. Ini memungkinkan sinkronisasi antar tab.

JavaScript — Storage Events
// ===== Storage Event =====
// Event ini HANYA di-trigger di TAB LAIN!
// Tidak di-trigger di tab yang mengubah storage.

window.addEventListener('storage', (event) => {
    console.log('Storage berubah!');
    console.log('  Key:', event.key);           // Key yang berubah
    console.log('  Old:', event.oldValue);      // Nilai lama
    console.log('  New:', event.newValue);      // Nilai baru
    console.log('  URL:', event.url);           // URL halaman yang mengubah
    console.log('  StorageArea:', event.storageArea); // localStorage object
});

// ===== Contoh: Auto-Logout di Semua Tab =====
// Tab 1: User logout → set flag
function logout() {
    localStorage.setItem('logout-event', Date.now().toString());
    localStorage.removeItem('auth-token');
    window.location.href = '/login';
}

// Tab 2, 3, dll: Listen untuk logout event
window.addEventListener('storage', (event) => {
    if (event.key === 'logout-event') {
        // User logout di tab lain → redirect ke login
        window.location.href = '/login';
    }
});

// ===== Contoh: Theme Sync antar Tab =====
// Tab 1: Ganti tema
function changeTheme(theme) {
    localStorage.setItem('theme', theme);
    applyTheme(theme); // Langsung terapkan di tab ini
}

// Tab 2, 3, dll: Ikut ganti tema
window.addEventListener('storage', (event) => {
    if (event.key === 'theme') {
        applyTheme(event.newValue);
    }
});

function applyTheme(theme) {
    document.documentElement.setAttribute('data-theme', theme);
}

5. Cookies vs Storage

JavaScript — Cookies
// ===== Cookies =====
// Cookies dikirim ke server SETIAP request (overhead!)
// Kapasitas: ~4KB per cookie

// Set cookie
document.cookie = "username=Beebane; expires=Fri, 31 Dec 2026 23:59:59 GMT; path=/";
document.cookie = "theme=dark; max-age=86400; path=/"; // 1 hari
document.cookie = "session=abc123; path=/; SameSite=Strict; Secure";

// Set cookie dengan JavaScript Date
const expiry = new Date();
expiry.setDate(expiry.getDate() + 7); // 7 hari
document.cookie = `token=xyz; expires=${expiry.toUTCString()}; path=/`;

// Baca cookies (satu string besar!)
console.log(document.cookie);
// → "username=Beebane; theme=dark; session=abc123; token=xyz"

// Parse cookies
function getCookie(name) {
    const cookies = document.cookie.split(';');
    for (const cookie of cookies) {
        const [key, value] = cookie.trim().split('=');
        if (key === name) return decodeURIComponent(value);
    }
    return null;
}

console.log(getCookie('username')); // → 'Beebane'

// Hapus cookie (set expires ke masa lalu)
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/";

// ===== Cookie Attributes =====
// path=/        → Berlaku untuk semua path
// domain=.example.com → Berlaku untuk subdomain
// max-age=3600  → Berlaku 3600 detik
// expires=DATE  → Sampai tanggal tertentu
// secure        → Hanya dikirim via HTTPS
// HttpOnly       → Tidak bisa diakses JavaScript (server only!)
// SameSite=Lax  → CSRF protection (default modern browsers)
// SameSite=Strict → Cookie hanya untuk same-site request

// ===== Perbandingan: Kapan Menggunakan Cookie? =====
// Cookie:    Autentikasi, CSRF token, data yang perlu dikirim ke server
// localStorage: Preferences, cache, data yang TIDAK perlu ke server
// sessionStorage: Draft form, state sementara per tab
SkenarioGunakanAlasan
Token autentikasiHttpOnly CookieAman dari XSS, dikirim otomatis
User preferenceslocalStoragePersisten, tidak perlu ke server
Form draftsessionStorageHilang saat tab ditutup
Shopping cartlocalStorage / IndexedDBPersisten, bisa besar
API response cacheIndexedDB / Cache APIData besar, struktur kompleks
Theme settinglocalStoragePersisten, kecil

6. IndexedDB Dasar

IndexedDB adalah database bawaan browser yang powerful — bisa menyimpan data dalam jumlah besar, mendukung indexing, transaksi, dan query kompleks. Ini adalah solusi terbaik untuk penyimpanan data offline yang besar dan terstruktur.

JavaScript — IndexedDB Dasar
// ===== Membuka Database =====
// IndexedDB bersifat async dan berbasis event/callback

function openDatabase() {
    return new Promise((resolve, reject) => {
        // Buka atau buat database baru
        const request = indexedDB.open('BeebaneDB', 1);

        // Dipanggil saat database perlu di-upgrade (versi baru)
        request.onupgradeneeded = (event) => {
            const db = event.target.result;
            console.log('Upgrading database...');

            // Buat object store (mirip "tabel" di SQL)
            if (!db.objectStoreNames.contains('users')) {
                const store = db.createObjectStore('users', {
                    keyPath: 'id',          // Primary key
                    autoIncrement: true     // Auto-increment ID
                });

                // Buat index untuk pencarian
                store.createIndex('email', 'email', { unique: true });
                store.createIndex('nama', 'nama', { unique: false });
                store.createIndex('role', 'role', { unique: false });
            }

            if (!db.objectStoreNames.contains('posts')) {
                const postStore = db.createObjectStore('posts', {
                    keyPath: 'id',
                    autoIncrement: true
                });
                postStore.createIndex('authorId', 'authorId');
                postStore.createIndex('createdAt', 'createdAt');
            }
        };

        request.onsuccess = (event) => {
            const db = event.target.result;
            console.log('Database berhasil dibuka!');
            resolve(db);
        };

        request.onerror = (event) => {
            console.error('Gagal membuka database:', event.target.error);
            reject(event.target.error);
        };
    });
}
JavaScript — CRUD Operations
// ===== CREATE: Menambah Data =====
async function addUser(user) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        // Mulai transaksi (readwrite)
        const tx = db.transaction('users', 'readwrite');
        const store = tx.objectStore('users');

        const request = store.add(user);

        request.onsuccess = () => {
            console.log('User ditambahkan, ID:', request.result);
            resolve(request.result); // ID yang di-generate
        };

        request.onerror = () => {
            console.error('Gagal menambah user:', request.error);
            reject(request.error);
        };
    });
}

// Penggunaan:
const newId = await addUser({
    nama: 'Beebane',
    email: 'bee@example.com',
    role: 'admin',
    createdAt: new Date()
});

// ===== READ: Membaca Data =====
async function getUserById(id) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readonly');
        const store = tx.objectStore('users');
        const request = store.get(id);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Membaca semua data
async function getAllUsers() {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readonly');
        const store = tx.objectStore('users');
        const request = store.getAll();

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// ===== UPDATE: Memperbarui Data =====
async function updateUser(user) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readwrite');
        const store = tx.objectStore('users');
        const request = store.put(user); // put = insert atau update

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// ===== DELETE: Menghapus Data =====
async function deleteUser(id) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readwrite');
        const store = tx.objectStore('users');
        const request = store.delete(id);

        request.onsuccess = () => resolve(true);
        request.onerror = () => reject(request.error);
    });
}

7. IndexedDB Lanjutan

Menggunakan Index

JavaScript — IndexedDB Index & Query
// ===== Query menggunakan Index =====
async function getUserByEmail(email) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readonly');
        const store = tx.objectStore('users');
        const index = store.index('email'); // Gunakan index 'email'
        const request = index.get(email);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Cari semua admin
async function getUsersByRole(role) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readonly');
        const store = tx.objectStore('users');
        const index = store.index('role');
        const request = index.getAll(role); // Filter by role

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// ===== Cursor: Iterasi Data =====
async function iterateUsers() {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readonly');
        const store = tx.objectStore('users');
        const results = [];

        // Buka cursor untuk iterasi
        const request = store.openCursor();

        request.onsuccess = (event) => {
            const cursor = event.target.result;

            if (cursor) {
                // Ada data
                results.push(cursor.value);
                cursor.continue(); // Lanjut ke data berikutnya
            } else {
                // Iterasi selesai
                resolve(results);
            }
        };

        request.onerror = () => reject(request.error);
    });
}

// ===== Range Query =====
async function getUsersInRange(startId, endId) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readonly');
        const store = tx.objectStore('users');

        // IDBKeyRange untuk range query
        const range = IDBKeyRange.bound(startId, endId);
        const request = store.getAll(range);

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

// Range yang lebih fleksibel:
const lowerBound = IDBKeyRange.lowerBound(5);      // id >= 5
const upperBound = IDBKeyRange.upperBound(10);      // id <= 10
const only5 = IDBKeyRange.only(5);                  // id === 5
const exclude5 = IDBKeyRange.lowerBound(5, true);   // id > 5 (exclude 5)

Transaksi & Batch Operations

JavaScript — Transaksi IndexedDB
// ===== Transaksi: Atomic Operations =====
// Transaksi memastikan semua operasi berhasil atau semua gagal

async function transferData(fromId, toId, amount) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        // Satu transaksi untuk kedua operasi
        const tx = db.transaction('users', 'readwrite');
        const store = tx.objectStore('users');

        const getFrom = store.get(fromId);
        const getTo = store.get(toId);

        getFrom.onsuccess = () => {
            getTo.onsuccess = () => {
                const from = getFrom.result;
                const to = getTo.result;

                if (!from || !to) {
                    tx.abort(); // Batalkan semua!
                    reject(new Error('User tidak ditemukan'));
                    return;
                }

                from.saldo -= amount;
                to.saldo += amount;

                store.put(from);
                store.put(to);
            };
        };

        // Transaksi selesai (semua operasi berhasil)
        tx.oncomplete = () => {
            console.log('Transfer berhasil!');
            resolve(true);
        };

        // Transaksi gagal (salah satu operasi error)
        tx.onerror = () => {
            console.error('Transfer gagal:', tx.error);
            reject(tx.error);
        };
    });
}

// ===== Batch Insert =====
async function batchInsert(storeName, items) {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction(storeName, 'readwrite');
        const store = tx.objectStore(storeName);

        items.forEach(item => store.add(item));

        tx.oncomplete = () => {
            console.log(`${items.length} item ditambahkan`);
            resolve(true);
        };

        tx.onerror = () => reject(tx.error);
    });
}

// ===== Count & Clear =====
async function countUsers() {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readonly');
        const store = tx.objectStore('users');
        const request = store.count();

        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
    });
}

async function clearAllUsers() {
    const db = await openDatabase();
    return new Promise((resolve, reject) => {
        const tx = db.transaction('users', 'readwrite');
        const store = tx.objectStore('users');
        const request = store.clear();

        request.onsuccess = () => resolve(true);
        request.onerror = () => reject(request.error);
    });
}

8. Storage Wrapper Library

JavaScript — Universal Storage Wrapper
// ===== Storage Wrapper yang Clean =====
class Storage {
    constructor(storage = localStorage) {
        this.storage = storage;
    }

    set(key, value, options = {}) {
        try {
            const data = {
                value,
                timestamp: Date.now(),
                expiry: options.expiry
                    ? Date.now() + options.expiry
                    : null
            };
            this.storage.setItem(key, JSON.stringify(data));
            return true;
        } catch (error) {
            console.error('Storage set error:', error);
            return false;
        }
    }

    get(key, defaultValue = null) {
        try {
            const raw = this.storage.getItem(key);
            if (!raw) return defaultValue;

            const data = JSON.parse(raw);

            // Cek expiry
            if (data.expiry && Date.now() > data.expiry) {
                this.storage.removeItem(key);
                return defaultValue;
            }

            return data.value;
        } catch (error) {
            console.error('Storage get error:', error);
            return defaultValue;
        }
    }

    remove(key) {
        this.storage.removeItem(key);
    }

    has(key) {
        return this.get(key) !== null;
    }

    clear() {
        this.storage.clear();
    }

    keys() {
        return Object.keys(this.storage);
    }

    // Tambah item ke array yang tersimpan
    push(key, item) {
        const arr = this.get(key, []);
        arr.push(item);
        return this.set(key, arr);
    }

    // Hapus item dari array
    removeItem(key, predicate) {
        const arr = this.get(key, []);
        const filtered = arr.filter(item => !predicate(item));
        return this.set(key, filtered);
    }

    // Increment counter
    increment(key, amount = 1) {
        const current = this.get(key, 0);
        return this.set(key, current + amount);
    }
}

// Penggunaan:
const store = new Storage(localStorage);
const session = new Storage(sessionStorage);

// Simpan dengan expiry (1 jam)
store.set('token', 'abc123', { expiry: 60 * 60 * 1000 });

// Baca (null jika expired)
const token = store.get('token');

// Array operations
store.push('favorites', 'JavaScript');
store.push('favorites', 'Python');
const favs = store.get('favorites'); // ['JavaScript', 'Python']

// Counter
store.increment('pageViews');
store.increment('pageViews');
console.log(store.get('pageViews')); // 2

9. Best Practices

Handling QuotaExceededError

JavaScript — Storage Quota Management
// ===== Cek Storage yang Tersedia =====
async function checkStorageQuota() {
    if (navigator.storage && navigator.storage.estimate) {
        const estimate = await navigator.storage.estimate();
        const usedMB = (estimate.usage / 1024 / 1024).toFixed(2);
        const quotaMB = (estimate.quota / 1024 / 1024).toFixed(2);
        const percent = ((estimate.usage / estimate.quota) * 100).toFixed(1);

        console.log(`Storage terpakai: ${usedMB} MB dari ${quotaMB} MB (${percent}%)`);
        return { used: usedMB, quota: quotaMB, percent };
    }
    console.log('Storage estimate API tidak tersedia');
    return null;
}

// ===== Tangani QuotaExceededError =====
function safeSetItem(key, value) {
    try {
        localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
        if (error.name === 'QuotaExceededError' ||
            error.code === 22 ||
            error.code === 1014) {
            console.error('Storage penuh! Membersihkan data lama...');
            cleanupOldData();
            // Coba lagi
            try {
                localStorage.setItem(key, JSON.stringify(value));
            } catch (e2) {
                console.error('Masih gagal setelah cleanup');
                // Fallback: gunakan IndexedDB
                saveToIndexedDB(key, value);
            }
        }
    }
}

function cleanupOldData() {
    // Hapus item yang sudah expired atau tidak penting
    const keys = Object.keys(localStorage);
    for (const key of keys) {
        try {
            const data = JSON.parse(localStorage.getItem(key));
            if (data && data.expiry && Date.now() > data.expiry) {
                localStorage.removeItem(key);
                console.log('Menghapus expired:', key);
            }
        } catch {
            // Bukan format JSON → skip
        }
    }
}

// ===== Minta Persistent Storage =====
async function requestPersistentStorage() {
    if (navigator.storage && navigator.storage.persist) {
        const isPersisted = await navigator.storage.persisted();
        if (isPersisted) {
            console.log('Storage sudah persistent');
            return true;
        }

        const granted = await navigator.storage.persist();
        console.log(granted
            ? '✅ Persistent storage diizinkan!'
            : '❌ Persistent storage ditolak'
        );
        return granted;
    }
    return false;
}

Checklist Best Practices

PraktikPenjelasan
JSON.stringify/parseSelalu gunakan untuk localStorage/sessionStorage
Error handlingSelalu tangkap QuotaExceededError
Jangan simpan sensitifToken/password jangan di localStorage
NamespacingPrefix key: app_userName, app_theme
Expiry systemSimpan timestamp dan cek sebelum baca
IndexedDB untuk besarLebih dari 1MB → gunakan IndexedDB
Persistent storageMinta persistent agar tidak dihapus browser

10. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut:

Pertanyaan 1: Berapa kapasitas maksimal localStorage?

a) 4 KB
b) ~5-10 MB
c) Unlimited
d) 1 GB

Pertanyaan 2: Apa perbedaan utama antara localStorage dan sessionStorage?

a) localStorage lebih cepat
b) sessionStorage data hilang saat tab ditutup
c) sessionStorage lebih aman
d) localStorage hanya untuk string

Pertanyaan 3: Tipe data apa yang bisa disimpan di localStorage tanpa konversi?

a) Object
b) Array
c) String
d) Number

Pertanyaan 4: Mengapa TIDAK boleh menyimpan token autentikasi di localStorage?

a) Karena kapasitasnya terbatas
b) Karena bisa diakses oleh JavaScript (rentan XSS)
c) Karena datanya hilang saat refresh
d) Karena tidak mendukung format JSON

Pertanyaan 5: Keunggulan utama IndexedDB dibanding localStorage?

a) Lebih mudah digunakan
b) Bisa menyimpan data dalam jumlah besar dan mendukung indexing
c) Lebih aman
d) Data lebih cepat tersimpan
🔍 Zoom
100%
🎨 Tema