客户端存储

Cookie

Cookie是最早的客户端存储方式。

Cookie基础

// Cookie特点
// - 大小限制:约4KB
// - 每次HTTP请求都会发送
// - 可以设置过期时间
// - 可以设置域名和路径
// - 可以设置HttpOnly和Secure

// 创建Cookie
document.cookie = "name=东巴文";
document.cookie = "age=25";

// 读取Cookie
console.log(document.cookie);  // "name=东巴文; age=25"

// 注意:document.cookie不是字符串
// 赋值是添加,不是替换

Cookie属性

// 设置过期时间
document.cookie = "name=东巴文; expires=Fri, 31 Dec 2025 23:59:59 GMT";

// 使用max-age(秒)
document.cookie = "name=东巴文; max-age=3600";  // 1小时后过期

// 设置路径
document.cookie = "name=东巴文; path=/";

// 设置域名
document.cookie = "name=东巴文; domain=.db-w.cn";

// 设置Secure(仅HTTPS)
document.cookie = "name=东巴文; secure";

// 设置HttpOnly(JavaScript无法读取)
// 只能通过服务器设置
// Set-Cookie: name=东巴文; HttpOnly

// 设置SameSite
document.cookie = "name=东巴文; SameSite=Strict";

Cookie操作封装

const Cookie = {
    set(name, value, options = {}) {
        let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
        
        if (options.expires) {
            cookie += `; expires=${options.expires.toUTCString()}`;
        }
        
        if (options.maxAge) {
            cookie += `; max-age=${options.maxAge}`;
        }
        
        if (options.path) {
            cookie += `; path=${options.path}`;
        }
        
        if (options.domain) {
            cookie += `; domain=${options.domain}`;
        }
        
        if (options.secure) {
            cookie += "; secure";
        }
        
        if (options.sameSite) {
            cookie += `; SameSite=${options.sameSite}`;
        }
        
        document.cookie = cookie;
    },
    
    get(name) {
        const cookies = document.cookie.split("; ");
        
        for (const cookie of cookies) {
            const [key, value] = cookie.split("=");
            if (decodeURIComponent(key) === name) {
                return decodeURIComponent(value);
            }
        }
        
        return null;
    },
    
    remove(name, options = {}) {
        const expires = new Date(0);
        this.set(name, "", { ...options, expires });
    },
    
    has(name) {
        return this.get(name) !== null;
    },
    
    getAll() {
        const result = {};
        const cookies = document.cookie.split("; ");
        
        for (const cookie of cookies) {
            if (cookie) {
                const [key, value] = cookie.split("=");
                result[decodeURIComponent(key)] = decodeURIComponent(value);
            }
        }
        
        return result;
    }
};

// 使用
Cookie.set("name", "东巴文", { maxAge: 3600, path: "/" });
console.log(Cookie.get("name"));  // "东巴文"
Cookie.remove("name");

localStorage

localStorage提供持久化的本地存储。

基本用法

// 存储数据
localStorage.setItem("name", "东巴文");
localStorage.setItem("age", "25");

// 读取数据
console.log(localStorage.getItem("name"));  // "东巴文"
console.log(localStorage.getItem("age"));   // "25"

// 删除数据
localStorage.removeItem("name");

// 清空所有数据
localStorage.clear();

// 获取键名
localStorage.setItem("a", "1");
localStorage.setItem("b", "2");
console.log(localStorage.key(0));  // "a" 或 "b"
console.log(localStorage.length);  // 2

存储对象

// localStorage只能存储字符串
// 需要JSON序列化

const user = {
    name: "东巴文",
    email: "info@db-w.cn"
};

// 存储
localStorage.setItem("user", JSON.stringify(user));

// 读取
const stored = localStorage.getItem("user");
const parsedUser = stored ? JSON.parse(stored) : null;
console.log(parsedUser.name);  // "东巴文"

localStorage封装

const Storage = {
    set(key, value) {
        try {
            localStorage.setItem(key, JSON.stringify(value));
            return true;
        } catch (e) {
            console.error("存储失败:", e);
            return false;
        }
    },
    
    get(key, defaultValue = null) {
        try {
            const value = localStorage.getItem(key);
            return value ? JSON.parse(value) : defaultValue;
        } catch (e) {
            console.error("读取失败:", e);
            return defaultValue;
        }
    },
    
    remove(key) {
        localStorage.removeItem(key);
    },
    
    clear() {
        localStorage.clear();
    },
    
    has(key) {
        return localStorage.getItem(key) !== null;
    }
};

// 使用
Storage.set("user", { name: "东巴文" });
console.log(Storage.get("user"));  // { name: "东巴文" }

sessionStorage

sessionStorage提供会话级别的存储。

基本用法

// 与localStorage API相同
sessionStorage.setItem("token", "abc123");
console.log(sessionStorage.getItem("token"));  // "abc123"
sessionStorage.removeItem("token");
sessionStorage.clear();

// 区别:
// - sessionStorage在页面关闭后清除
// - localStorage永久保存
// - sessionStorage在不同标签页间隔离
// - localStorage在同源页面间共享

使用场景

// 场景1:表单数据临时保存
const form = document.querySelector("form");

// 输入时保存
form.addEventListener("input", function() {
    const formData = new FormData(this);
    const data = Object.fromEntries(formData);
    sessionStorage.setItem("formData", JSON.stringify(data));
});

// 页面加载时恢复
const savedData = sessionStorage.getItem("formData");
if (savedData) {
    const data = JSON.parse(savedData);
    Object.entries(data).forEach(([name, value]) => {
        const field = form.elements[name];
        if (field) field.value = value;
    });
}

// 场景2:一次性消息
function showFlashMessage(message) {
    sessionStorage.setItem("flashMessage", message);
}

// 页面加载时显示
const flashMessage = sessionStorage.getItem("flashMessage");
if (flashMessage) {
    alert(flashMessage);
    sessionStorage.removeItem("flashMessage");
}

存储事件

监听storage变化。

storage事件

// storage事件在同源的其他页面触发
window.addEventListener("storage", function(e) {
    console.log("键:", e.key);
    console.log("旧值:", e.oldValue);
    console.log("新值:", e.newValue);
    console.log("URL:", e.url);
    console.log("存储区域:", e.storageArea);
});

// 在另一个标签页执行
localStorage.setItem("name", "东巴文");
// 第一个标签页会收到storage事件

跨标签页通信

// 标签页通信示例
const TabMessenger = {
    send(type, data) {
        localStorage.setItem("message", JSON.stringify({
            type,
            data,
            timestamp: Date.now()
        }));
        localStorage.removeItem("message");  // 触发事件
    },
    
    on(type, callback) {
        window.addEventListener("storage", function(e) {
            if (e.key === "message" && e.newValue) {
                const message = JSON.parse(e.newValue);
                if (message.type === type) {
                    callback(message.data);
                }
            }
        });
    }
};

// 使用
TabMessenger.on("notification", function(data) {
    showNotification(data.message);
});

// 在另一个标签页发送
TabMessenger.send("notification", { message: "新消息到达" });

IndexedDB

浏览器内置的NoSQL数据库。

基本概念

// IndexedDB特点
// - 存储大量结构化数据
// - 支持索引和事务
// - 异步API
// - 同源策略

// 打开数据库
const request = indexedDB.open("MyDatabase", 1);

request.onerror = function() {
    console.error("数据库打开失败");
};

request.onsuccess = function() {
    const db = request.result;
    console.log("数据库打开成功");
};

request.onupgradeneeded = function(e) {
    const db = e.target.result;
    
    // 创建对象存储(表)
    if (!db.objectStoreNames.contains("users")) {
        const store = db.createObjectStore("users", { keyPath: "id" });
        
        // 创建索引
        store.createIndex("name", "name", { unique: false });
        store.createIndex("email", "email", { unique: true });
    }
};

增删改查

class IndexedDBHelper {
    constructor(dbName, version) {
        this.dbName = dbName;
        this.version = version;
        this.db = null;
    }
    
    open() {
        return new Promise((resolve, reject) => {
            const request = indexedDB.open(this.dbName, this.version);
            
            request.onerror = () => reject(request.error);
            request.onsuccess = () => {
                this.db = request.result;
                resolve(this.db);
            };
            request.onupgradeneeded = (e) => {
                const db = e.target.result;
                if (!db.objectStoreNames.contains("items")) {
                    db.createObjectStore("items", { keyPath: "id" });
                }
            };
        });
    }
    
    add(storeName, data) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(storeName, "readwrite");
            const store = transaction.objectStore(storeName);
            const request = store.add(data);
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    get(storeName, key) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(storeName, "readonly");
            const store = transaction.objectStore(storeName);
            const request = store.get(key);
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    update(storeName, data) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(storeName, "readwrite");
            const store = transaction.objectStore(storeName);
            const request = store.put(data);
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
    
    delete(storeName, key) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(storeName, "readwrite");
            const store = transaction.objectStore(storeName);
            const request = store.delete(key);
            
            request.onsuccess = () => resolve();
            request.onerror = () => reject(request.error);
        });
    }
    
    getAll(storeName) {
        return new Promise((resolve, reject) => {
            const transaction = this.db.transaction(storeName, "readonly");
            const store = transaction.objectStore(storeName);
            const request = store.getAll();
            
            request.onsuccess = () => resolve(request.result);
            request.onerror = () => reject(request.error);
        });
    }
}

// 使用
const db = new IndexedDBHelper("MyDB", 1);

async function demo() {
    await db.open();
    
    await db.add("items", { id: 1, name: "东巴文" });
    await db.add("items", { id: 2, name: "db-w.cn" });
    
    const item = await db.get("items", 1);
    console.log(item);  // { id: 1, name: "东巴文" }
    
    await db.update("items", { id: 1, name: "新名称" });
    await db.delete("items", 2);
    
    const all = await db.getAll("items");
    console.log(all);
}

demo();

存储限制

各种存储方式的限制。

存储容量

// 检测存储配额
if (navigator.storage && navigator.storage.estimate) {
    navigator.storage.estimate().then(estimate => {
        console.log("已使用:", estimate.usage, "字节");
        console.log("配额:", estimate.quota, "字节");
        console.log("使用率:", (estimate.usage / estimate.quota * 100).toFixed(2) + "%");
    });
}

// 检测存储是否持久化
if (navigator.storage && navigator.storage.persist) {
    navigator.storage.persist().then(persisted => {
        if (persisted) {
            console.log("存储已持久化");
        }
    });
}

存储方式对比

存储方式 容量 过期时间 跨标签页 发送到服务器
Cookie ~4KB 可设置 共享
localStorage ~5MB 永久 共享
sessionStorage ~5MB 会话 隔离
IndexedDB ~50MB+ 永久 共享

存储异常处理

function safeSetItem(key, value) {
    try {
        localStorage.setItem(key, value);
        return true;
    } catch (e) {
        if (e.name === "QuotaExceededError") {
            console.error("存储空间已满");
            // 清理旧数据
            localStorage.clear();
            // 重试
            try {
                localStorage.setItem(key, value);
                return true;
            } catch (e) {
                return false;
            }
        }
        return false;
    }
}

// 检测隐私模式
function isPrivateMode() {
    try {
        localStorage.setItem("test", "test");
        localStorage.removeItem("test");
        return false;
    } catch (e) {
        return true;
    }
}

下一步

掌握了客户端存储后,让我们继续学习:

  1. 回调函数 - 学习异步编程基础
  2. Promise - 学习Promise异步处理
  3. Async/Await - 学习async/await语法

东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文寄语:客户端存储是现代Web应用的重要组成部分,根据需求选择合适的存储方式:Cookie用于会话、localStorage用于持久数据、IndexedDB用于大量结构化数据。在 db-w.cn,我们帮你掌握数据存储技术!