现代Web存储技术(二):存储容量规划与传统方案对比

举报
Yeats_Liao 发表于 2025/11/17 08:54:01 2025/11/17
【摘要】 除了三大主力存储技术,浏览器还有一些传统存储方式。虽然它们有各自的局限性,但在特定场景下仍然有用武之地。本文将详细介绍这些传统存储方式,以及如何管理浏览器存储容量。 1 传统存储方式:能用但有坑除了Cache API、IndexedDB和OPFS这三大主力,浏览器还有一些老牌存储方式。它们不是不能用,但都有各自的问题。 1.1 LocalStorage:简单但性能差什么时候还在用?• 存个主...

除了三大主力存储技术,浏览器还有一些传统存储方式。虽然它们有各自的局限性,但在特定场景下仍然有用武之地。本文将详细介绍这些传统存储方式,以及如何管理浏览器存储容量。

1 传统存储方式:能用但有坑

除了Cache API、IndexedDB和OPFS这三大主力,浏览器还有一些老牌存储方式。它们不是不能用,但都有各自的问题。

1.1 LocalStorage:简单但性能差

什么时候还在用?
• 存个主题设置(深色/浅色模式)
• 记住用户的语言偏好
• 保存简单的表单数据

问题在哪?
• 会卡页面:读写数据时整个页面都得等着
• 容量太小:只有5MB,存不了什么大东西
• 只能存文本:图片、文件什么的都存不了

实际体验
你有没有遇到过网页突然卡住几秒?很可能就是某个网站在用LocalStorage存大量数据。

使用示例

// 主题设置管理
class ThemeManager {
  constructor() {
    this.THEME_KEY = 'app-theme';
    this.LANGUAGE_KEY = 'app-language';
  }
  
  // 设置主题
  setTheme(theme) {
    try {
      localStorage.setItem(this.THEME_KEY, theme);
      document.body.className = `theme-${theme}`;
      console.log(`主题已切换为: ${theme}`);
    } catch (error) {
      console.error('设置主题失败:', error);
      // 可能是存储空间不足或隐私模式
      this.handleStorageError(error);
    }
  }
  
  // 获取主题
  getTheme() {
    try {
      return localStorage.getItem(this.THEME_KEY) || 'light';
    } catch (error) {
      console.error('获取主题失败:', error);
      return 'light'; // 默认主题
    }
  }
  
  // 设置语言
  setLanguage(language) {
    try {
      localStorage.setItem(this.LANGUAGE_KEY, language);
      // 触发语言切换事件
      window.dispatchEvent(new CustomEvent('languageChange', {
        detail: { language }
      }));
    } catch (error) {
      console.error('设置语言失败:', error);
    }
  }
  
  // 获取语言
  getLanguage() {
    try {
      return localStorage.getItem(this.LANGUAGE_KEY) || 'zh-CN';
    } catch (error) {
      console.error('获取语言失败:', error);
      return 'zh-CN';
    }
  }
  
  // 处理存储错误
  handleStorageError(error) {
    if (error.name === 'QuotaExceededError') {
      alert('存储空间不足,请清理浏览器数据');
    } else if (error.name === 'SecurityError') {
      console.warn('隐私模式下无法使用LocalStorage');
    }
  }
  
  // 清理所有设置
  clearSettings() {
    try {
      localStorage.removeItem(this.THEME_KEY);
      localStorage.removeItem(this.LANGUAGE_KEY);
      console.log('设置已清理');
    } catch (error) {
      console.error('清理设置失败:', error);
    }
  }
}

// 表单数据自动保存
class FormAutoSave {
  constructor(formId, saveKey) {
    this.form = document.getElementById(formId);
    this.saveKey = saveKey;
    this.debounceTimer = null;
    this.init();
  }
  
  init() {
    if (!this.form) return;
    
    // 页面加载时恢复数据
    this.restoreFormData();
    
    // 监听表单变化
    this.form.addEventListener('input', (e) => {
      this.debounceAutoSave();
    });
    
    // 表单提交时清理保存的数据
    this.form.addEventListener('submit', () => {
      this.clearSavedData();
    });
  }
  
  // 防抖自动保存
  debounceAutoSave() {
    clearTimeout(this.debounceTimer);
    this.debounceTimer = setTimeout(() => {
      this.saveFormData();
    }, 1000); // 1秒后保存
  }
  
  // 保存表单数据
  saveFormData() {
    try {
      const formData = new FormData(this.form);
      const data = {};
      
      for (let [key, value] of formData.entries()) {
        data[key] = value;
      }
      
      localStorage.setItem(this.saveKey, JSON.stringify(data));
      console.log('表单数据已自动保存');
    } catch (error) {
      console.error('保存表单数据失败:', error);
    }
  }
  
  // 恢复表单数据
  restoreFormData() {
    try {
      const savedData = localStorage.getItem(this.saveKey);
      if (!savedData) return;
      
      const data = JSON.parse(savedData);
      
      for (let [key, value] of Object.entries(data)) {
        const input = this.form.querySelector(`[name="${key}"]`);
        if (input) {
          input.value = value;
        }
      }
      
      console.log('表单数据已恢复');
    } catch (error) {
      console.error('恢复表单数据失败:', error);
    }
  }
  
  // 清理保存的数据
  clearSavedData() {
    try {
      localStorage.removeItem(this.saveKey);
      console.log('已清理保存的表单数据');
    } catch (error) {
      console.error('清理数据失败:', error);
    }
  }
}

// 使用示例
const themeManager = new ThemeManager();
const formAutoSave = new FormAutoSave('contact-form', 'contact-form-data');

// 初始化主题
document.addEventListener('DOMContentLoaded', () => {
  const savedTheme = themeManager.getTheme();
  themeManager.setTheme(savedTheme);
});

1.2 SessionStorage:用完就扔

适合存什么?
• 表单填到一半的内容(防止误关页面)
• 当前页面的临时状态
• 购物车里的商品(关闭页面就清空)

特点
• 关闭标签页就没了,很适合临时数据
• 同样会卡页面,同样只有5MB

使用示例

// 页面状态管理
class PageStateManager {
  constructor() {
    this.STATE_KEY = 'page-state';
    this.init();
  }
  
  init() {
    // 页面加载时恢复状态
    window.addEventListener('load', () => {
      this.restorePageState();
    });
    
    // 页面卸载时保存状态
    window.addEventListener('beforeunload', () => {
      this.savePageState();
    });
  }
  
  // 保存页面状态
  savePageState() {
    try {
      const state = {
        scrollPosition: window.scrollY,
        timestamp: Date.now(),
        activeTab: document.querySelector('.tab.active')?.dataset.tab,
        searchQuery: document.querySelector('#search-input')?.value,
        filters: this.getActiveFilters()
      };
      
      sessionStorage.setItem(this.STATE_KEY, JSON.stringify(state));
      console.log('页面状态已保存');
    } catch (error) {
      console.error('保存页面状态失败:', error);
    }
  }
  
  // 恢复页面状态
  restorePageState() {
    try {
      const savedState = sessionStorage.getItem(this.STATE_KEY);
      if (!savedState) return;
      
      const state = JSON.parse(savedState);
      
      // 恢复滚动位置
      if (state.scrollPosition) {
        window.scrollTo(0, state.scrollPosition);
      }
      
      // 恢复活动标签
      if (state.activeTab) {
        const tab = document.querySelector(`[data-tab="${state.activeTab}"]`);
        if (tab) {
          tab.click();
        }
      }
      
      // 恢复搜索查询
      if (state.searchQuery) {
        const searchInput = document.querySelector('#search-input');
        if (searchInput) {
          searchInput.value = state.searchQuery;
        }
      }
      
      // 恢复筛选器
      if (state.filters) {
        this.restoreFilters(state.filters);
      }
      
      console.log('页面状态已恢复');
    } catch (error) {
      console.error('恢复页面状态失败:', error);
    }
  }
  
  // 获取当前激活的筛选器
  getActiveFilters() {
    const filters = {};
    document.querySelectorAll('.filter-checkbox:checked').forEach(checkbox => {
      filters[checkbox.name] = checkbox.value;
    });
    return filters;
  }
  
  // 恢复筛选器状态
  restoreFilters(filters) {
    for (let [name, value] of Object.entries(filters)) {
      const checkbox = document.querySelector(`input[name="${name}"][value="${value}"]`);
      if (checkbox) {
        checkbox.checked = true;
      }
    }
  }
}

// 临时购物车管理
class TempShoppingCart {
  constructor() {
    this.CART_KEY = 'temp-shopping-cart';
  }
  
  // 添加商品到购物车
  addItem(product) {
    try {
      const cart = this.getCart();
      const existingItem = cart.find(item => item.id === product.id);
      
      if (existingItem) {
        existingItem.quantity += 1;
      } else {
        cart.push({ ...product, quantity: 1 });
      }
      
      sessionStorage.setItem(this.CART_KEY, JSON.stringify(cart));
      this.updateCartUI();
      console.log('商品已添加到购物车');
    } catch (error) {
      console.error('添加商品失败:', error);
    }
  }
  
  // 获取购物车内容
  getCart() {
    try {
      const cart = sessionStorage.getItem(this.CART_KEY);
      return cart ? JSON.parse(cart) : [];
    } catch (error) {
      console.error('获取购物车失败:', error);
      return [];
    }
  }
  
  // 移除商品
  removeItem(productId) {
    try {
      const cart = this.getCart();
      const updatedCart = cart.filter(item => item.id !== productId);
      sessionStorage.setItem(this.CART_KEY, JSON.stringify(updatedCart));
      this.updateCartUI();
      console.log('商品已移除');
    } catch (error) {
      console.error('移除商品失败:', error);
    }
  }
  
  // 清空购物车
  clearCart() {
    try {
      sessionStorage.removeItem(this.CART_KEY);
      this.updateCartUI();
      console.log('购物车已清空');
    } catch (error) {
      console.error('清空购物车失败:', error);
    }
  }
  
  // 更新购物车UI
  updateCartUI() {
    const cart = this.getCart();
    const cartCount = cart.reduce((total, item) => total + item.quantity, 0);
    
    const cartBadge = document.querySelector('.cart-badge');
    if (cartBadge) {
      cartBadge.textContent = cartCount;
      cartBadge.style.display = cartCount > 0 ? 'block' : 'none';
    }
  }
}

// 使用示例
const pageStateManager = new PageStateManager();
const tempCart = new TempShoppingCart();

1.3 Cookies:古老但必需

现在主要用来干嘛?
• 存登录状态(Session ID)
• 记住"下次自动登录"
• 广告追踪(虽然大家都讨厌)

为什么不用它存其他东西?
• 太小了:每个Cookie最多4KB
• 拖慢网速:每次请求都会把所有Cookie发给服务器
• 不安全:容易被脚本读取(除非设置HttpOnly)

真实案例
某些网站Cookie太多,光是发送Cookie就要几KB,拖慢了整个网站的加载速度。

使用示例

// Cookie管理工具类
class CookieManager {
  // 设置Cookie
  static setCookie(name, value, options = {}) {
    const {
      expires = null,
      maxAge = null,
      path = '/',
      domain = null,
      secure = false,
      sameSite = 'Lax'
    } = options;
    
    let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
    
    if (expires) {
      cookieString += `; expires=${expires.toUTCString()}`;
    }
    
    if (maxAge) {
      cookieString += `; max-age=${maxAge}`;
    }
    
    cookieString += `; path=${path}`;
    
    if (domain) {
      cookieString += `; domain=${domain}`;
    }
    
    if (secure) {
      cookieString += '; secure';
    }
    
    cookieString += `; samesite=${sameSite}`;
    
    document.cookie = cookieString;
    console.log(`Cookie已设置: ${name}`);
  }
  
  // 获取Cookie
  static getCookie(name) {
    const cookies = document.cookie.split(';');
    
    for (let cookie of cookies) {
      const [cookieName, cookieValue] = cookie.trim().split('=');
      if (decodeURIComponent(cookieName) === name) {
        return decodeURIComponent(cookieValue);
      }
    }
    
    return null;
  }
  
  // 删除Cookie
  static deleteCookie(name, path = '/', domain = null) {
    let cookieString = `${encodeURIComponent(name)}=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=${path}`;
    
    if (domain) {
      cookieString += `; domain=${domain}`;
    }
    
    document.cookie = cookieString;
    console.log(`Cookie已删除: ${name}`);
  }
  
  // 获取所有Cookie
  static getAllCookies() {
    const cookies = {};
    const cookieArray = document.cookie.split(';');
    
    for (let cookie of cookieArray) {
      const [name, value] = cookie.trim().split('=');
      if (name && value) {
        cookies[decodeURIComponent(name)] = decodeURIComponent(value);
      }
    }
    
    return cookies;
  }
}

// 用户认证管理
class AuthManager {
  constructor() {
    this.TOKEN_KEY = 'auth_token';
    this.REMEMBER_KEY = 'remember_login';
  }
  
  // 登录
  login(token, rememberMe = false) {
    if (rememberMe) {
      // 记住登录状态30天
      const expires = new Date();
      expires.setDate(expires.getDate() + 30);
      
      CookieManager.setCookie(this.TOKEN_KEY, token, {
        expires,
        secure: true,
        sameSite: 'Strict'
      });
      
      CookieManager.setCookie(this.REMEMBER_KEY, 'true', {
        expires,
        secure: true,
        sameSite: 'Strict'
      });
    } else {
      // 会话Cookie,关闭浏览器就失效
      CookieManager.setCookie(this.TOKEN_KEY, token, {
        secure: true,
        sameSite: 'Strict'
      });
    }
    
    console.log('用户已登录');
  }
  
  // 登出
  logout() {
    CookieManager.deleteCookie(this.TOKEN_KEY);
    CookieManager.deleteCookie(this.REMEMBER_KEY);
    console.log('用户已登出');
  }
  
  // 检查登录状态
  isLoggedIn() {
    return CookieManager.getCookie(this.TOKEN_KEY) !== null;
  }
  
  // 获取认证令牌
  getToken() {
    return CookieManager.getCookie(this.TOKEN_KEY);
  }
  
  // 检查是否记住登录
  isRememberLogin() {
    return CookieManager.getCookie(this.REMEMBER_KEY) === 'true';
  }
}

// 使用示例
const authManager = new AuthManager();

// 登录时
function handleLogin(username, password, rememberMe) {
  // 假设这里调用登录API
  fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  })
  .then(response => response.json())
  .then(data => {
    if (data.token) {
      authManager.login(data.token, rememberMe);
      window.location.href = '/dashboard';
    }
  })
  .catch(error => {
    console.error('登录失败:', error);
  });
}

1.4 File System Access API:直接操作本地文件

这个比较特殊,用来干嘛?

  • VS Code网页版:直接编辑你电脑上的代码文件
  • 网页版视频编辑器:导入本地视频进行编辑
  • 在线图片编辑器:直接保存到你指定的文件夹

使用条件:

  • 用户必须主动选择文件或文件夹
  • 浏览器会弹出权限确认
  • 主要用于专业工具类网站

使用示例:

// 文件系统访问管理器
class FileSystemAccessManager {
  constructor() {
    this.supportedTypes = {
      text: {
        description: '文本文件',
        accept: {
          'text/plain': ['.txt'],
          'text/javascript': ['.js'],
          'text/html': ['.html'],
          'text/css': ['.css']
        }
      },
      image: {
        description: '图片文件',
        accept: {
          'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp']
        }
      },
      video: {
        description: '视频文件',
        accept: {
          'video/*': ['.mp4', '.webm', '.ogg']
        }
      }
    };
  }
  
  // 检查浏览器支持
  isSupported() {
    return 'showOpenFilePicker' in window;
  }
  
  // 选择并读取文件
  async openFile(type = 'text') {
    if (!this.isSupported()) {
      throw new Error('浏览器不支持File System Access API');
    }
    
    try {
      const [fileHandle] = await window.showOpenFilePicker({
        types: [this.supportedTypes[type]],
        multiple: false
      });
      
      const file = await fileHandle.getFile();
      const content = await file.text();
      
      return {
        handle: fileHandle,
        file,
        content,
        name: file.name,
        size: file.size,
        lastModified: file.lastModified
      };
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('用户取消了文件选择');
        return null;
      }
      throw error;
    }
  }
  
  // 选择多个文件
  async openMultipleFiles(type = 'image') {
    if (!this.isSupported()) {
      throw new Error('浏览器不支持File System Access API');
    }
    
    try {
      const fileHandles = await window.showOpenFilePicker({
        types: [this.supportedTypes[type]],
        multiple: true
      });
      
      const files = [];
      for (const handle of fileHandles) {
        const file = await handle.getFile();
        files.push({
          handle,
          file,
          name: file.name,
          size: file.size,
          lastModified: file.lastModified
        });
      }
      
      return files;
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('用户取消了文件选择');
        return [];
      }
      throw error;
    }
  }
  
  // 保存文件
  async saveFile(content, suggestedName = 'untitled.txt', type = 'text') {
    if (!this.isSupported()) {
      // 降级到下载
      this.downloadFile(content, suggestedName);
      return;
    }
    
    try {
      const fileHandle = await window.showSaveFilePicker({
        types: [this.supportedTypes[type]],
        suggestedName
      });
      
      const writable = await fileHandle.createWritable();
      await writable.write(content);
      await writable.close();
      
      console.log('文件已保存');
      return fileHandle;
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('用户取消了文件保存');
        return null;
      }
      throw error;
    }
  }
  
  // 降级方案:下载文件
  downloadFile(content, filename) {
    const blob = new Blob([content], { type: 'text/plain' });
    const url = URL.createObjectURL(blob);
    
    const a = document.createElement('a');
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    
    URL.revokeObjectURL(url);
    console.log('文件已下载');
  }
  
  // 选择目录
  async openDirectory() {
    if (!('showDirectoryPicker' in window)) {
      throw new Error('浏览器不支持目录选择');
    }
    
    try {
      const dirHandle = await window.showDirectoryPicker();
      return dirHandle;
    } catch (error) {
      if (error.name === 'AbortError') {
        console.log('用户取消了目录选择');
        return null;
      }
      throw error;
    }
  }
}

// 在线代码编辑器示例
class OnlineCodeEditor {
  constructor() {
    this.fsManager = new FileSystemAccessManager();
    this.currentFileHandle = null;
    this.editor = document.getElementById('code-editor');
  }
  
  // 打开文件
  async openFile() {
    try {
      const result = await this.fsManager.openFile('text');
      if (result) {
        this.currentFileHandle = result.handle;
        this.editor.value = result.content;
        document.title = `编辑器 - ${result.name}`;
        console.log(`已打开文件: ${result.name}`);
      }
    } catch (error) {
      console.error('打开文件失败:', error);
      alert('打开文件失败: ' + error.message);
    }
  }
  
  // 保存文件
  async saveFile() {
    try {
      const content = this.editor.value;
      
      if (this.currentFileHandle) {
        // 保存到当前文件
        const writable = await this.currentFileHandle.createWritable();
        await writable.write(content);
        await writable.close();
        console.log('文件已保存');
      } else {
        // 另存为新文件
        const handle = await this.fsManager.saveFile(content, 'code.js', 'text');
        if (handle) {
          this.currentFileHandle = handle;
        }
      }
    } catch (error) {
      console.error('保存文件失败:', error);
      alert('保存文件失败: ' + error.message);
    }
  }
  
  // 另存为
  async saveAsFile() {
    try {
      const content = this.editor.value;
      const handle = await this.fsManager.saveFile(content, 'code.js', 'text');
      if (handle) {
        this.currentFileHandle = handle;
      }
    } catch (error) {
      console.error('另存为失败:', error);
      alert('另存为失败: ' + error.message);
    }
  }
}

// 使用示例
const codeEditor = new OnlineCodeEditor();

// 绑定按钮事件
document.getElementById('open-btn')?.addEventListener('click', () => {
  codeEditor.openFile();
});

document.getElementById('save-btn')?.addEventListener('click', () => {
  codeEditor.saveFile();
});

document.getElementById('save-as-btn')?.addEventListener('click', () => {
  codeEditor.saveAsFile();
});

2 存储容量:比你想象的大得多

2.1 到底能存多少东西?

先说结论:现在的浏览器存储空间大得惊人,基本不用担心不够用。

Chrome浏览器:最大方

  • 如果你硬盘有500GB,Chrome最多能用400GB来存网页数据
  • 单个网站最多能用300GB
  • 隐身模式比较抠门,只给25GB

Firefox:也很慷慨

  • 能用一半的可用空间
  • 同一个网站(包括子域名)最多2GB

Safari:相对保守

  • 默认给1GB
  • 用完了会问你要不要再给200MB
  • 如果是添加到桌面的网页应用,空间会更大

2.2 这些数字意味着什么?

我们用具体例子来感受一下:

一个音乐网站能存多少歌?

  • 一首3分钟的歌(128kbps):约3MB
  • 1GB能存300多首歌
  • Chrome给的空间能存10万首歌!

一个新闻应用能存多少文章?

  • 一篇图文并茂的新闻:约50KB
  • 1GB能存2万篇文章
  • 够你看一辈子了

一个在线文档应用能存多少文档?

  • 一个10页的Word文档:约100KB
  • 1GB能存1万个文档
  • 比大多数人一辈子写的都多

实际项目中的存储使用:

拿一个典型的新闻应用举例:

  • 应用本身(HTML、CSS、JS、图标):10MB
  • 缓存100篇新闻文章:5MB
  • 用户设置、阅读历史:1MB
  • 总共才16MB,连浏览器限制的零头都不到

所以,容量基本不是问题,关键是怎么合理使用。

3 存储容量检测与管理

3.1 使用StorageManager API检测容量

现代浏览器提供了StorageManager API来查询存储使用情况:

// 存储容量监控器
class StorageMonitor {
  constructor() {
    this.updateInterval = null;
  }
  
  // 检查存储使用情况
  async checkStorageUsage() {
    if (!navigator.storage?.estimate) {
      console.warn('浏览器不支持StorageManager API');
      return null;
    }
    
    try {
      const estimate = await navigator.storage.estimate();
      
      const usage = {
        used: estimate.usage || 0,
        quota: estimate.quota || 0,
        usedMB: ((estimate.usage || 0) / 1024 / 1024).toFixed(2),
        quotaMB: ((estimate.quota || 0) / 1024 / 1024).toFixed(2),
        percentage: estimate.quota ? ((estimate.usage || 0) / estimate.quota * 100).toFixed(2) : 0,
        remaining: (estimate.quota || 0) - (estimate.usage || 0),
        remainingMB: (((estimate.quota || 0) - (estimate.usage || 0)) / 1024 / 1024).toFixed(2)
      };
      
      return usage;
    } catch (error) {
      console.error('检查存储使用情况失败:', error);
      return null;
    }
  }
  
  // 显示存储使用情况
  async displayStorageUsage() {
    const usage = await this.checkStorageUsage();
    if (!usage) return;
    
    console.log('=== 存储使用情况 ===');
    console.log(`已使用: ${usage.usedMB} MB`);
    console.log(`总配额: ${usage.quotaMB} MB`);
    console.log(`使用率: ${usage.percentage}%`);
    console.log(`剩余空间: ${usage.remainingMB} MB`);
    
    // 更新UI
    this.updateStorageUI(usage);
    
    return usage;
  }
  
  // 更新存储UI
  updateStorageUI(usage) {
    const progressBar = document.querySelector('.storage-progress');
    const usageText = document.querySelector('.storage-usage-text');
    
    if (progressBar) {
      progressBar.style.width = `${usage.percentage}%`;
      progressBar.className = `storage-progress ${
        usage.percentage > 90 ? 'danger' :
        usage.percentage > 70 ? 'warning' : 'normal'
      }`;
    }
    
    if (usageText) {
      usageText.textContent = `已使用 ${usage.usedMB} MB / ${usage.quotaMB} MB (${usage.percentage}%)`;
    }
  }
  
  // 开始监控
  startMonitoring(intervalMs = 30000) {
    this.stopMonitoring();
    
    this.updateInterval = setInterval(() => {
      this.displayStorageUsage();
    }, intervalMs);
    
    // 立即执行一次
    this.displayStorageUsage();
  }
  
  // 停止监控
  stopMonitoring() {
    if (this.updateInterval) {
      clearInterval(this.updateInterval);
      this.updateInterval = null;
    }
  }
  
  // 检查是否需要清理
  async needsCleanup(threshold = 80) {
    const usage = await this.checkStorageUsage();
    return usage && usage.percentage > threshold;
  }
  
  // 获取详细的存储分解
  async getStorageBreakdown() {
    const breakdown = {
      indexedDB: 0,
      cache: 0,
      localStorage: 0,
      sessionStorage: 0
    };
    
    try {
      // 估算LocalStorage大小
      let localStorageSize = 0;
      for (let key in localStorage) {
        if (localStorage.hasOwnProperty(key)) {
          localStorageSize += localStorage[key].length + key.length;
        }
      }
      breakdown.localStorage = localStorageSize;
      
      // 估算SessionStorage大小
      let sessionStorageSize = 0;
      for (let key in sessionStorage) {
        if (sessionStorage.hasOwnProperty(key)) {
          sessionStorageSize += sessionStorage[key].length + key.length;
        }
      }
      breakdown.sessionStorage = sessionStorageSize;
      
      // Cache API大小需要遍历所有缓存
      if ('caches' in window) {
        const cacheNames = await caches.keys();
        for (const cacheName of cacheNames) {
          const cache = await caches.open(cacheName);
          const requests = await cache.keys();
          // 这里只能估算,实际大小需要获取每个响应
          breakdown.cache += requests.length * 1024; // 粗略估算
        }
      }
      
      return breakdown;
    } catch (error) {
      console.error('获取存储分解失败:', error);
      return breakdown;
    }
  }
}

// 使用示例
const storageMonitor = new StorageMonitor();

// 开始监控存储使用情况
storageMonitor.startMonitoring(60000); // 每分钟检查一次

// 手动检查
document.getElementById('check-storage-btn')?.addEventListener('click', async () => {
  const usage = await storageMonitor.displayStorageUsage();
  
  if (await storageMonitor.needsCleanup()) {
    if (confirm('存储空间使用率较高,是否需要清理?')) {
      // 触发清理逻辑
      console.log('开始清理存储空间...');
    }
  }
});

3.2 开发者工具调试

在开发过程中,你可以使用浏览器开发者工具来:

  • 查看存储使用情况:Application → Storage
  • 清除存储数据:方便测试不同场景
  • 模拟存储限制:Chrome 88+支持自定义存储配额模拟

Chrome存储配额模拟步骤:

  1. 打开开发者工具
  2. 进入Application → Storage
  3. 勾选"Simulate custom storage quota"
  4. 输入想要模拟的存储限制

总结

传统存储方式虽然有各自的局限性,但在特定场景下仍然有用:

  • LocalStorage:适合存储简单的用户偏好设置
  • SessionStorage:适合临时状态和会话数据
  • Cookies:主要用于身份认证和服务器通信
  • File System Access API:专业工具的本地文件操作

关键是要了解每种技术的特点和限制,在合适的场景使用合适的技术。同时,现代浏览器提供了充足的存储空间,重点应该放在如何合理管理和使用这些空间上。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。