前端别再乱存数据了!这3种存储方案让你的应用快如闪电

作者:良山有风来日期:2025/10/28

你是不是也遇到过这样的场景?

用户刚填完一个超长的表单,不小心刷新了页面,所有数据都没了... 从接口请求的数据,用户每次操作都要重新加载,体验卡成PPT... 应用离线状态下完全无法使用,用户直接流失...

别担心!今天我就带你彻底解决这些问题。看完这篇文章,你将掌握一套完整的数据交互方案,让你的应用在任何网络状态下都能流畅运行。

为什么数据存储这么重要?

想象一下,你去超市购物,每次想买什么东西,都要跑回家查一下购物清单,然后再跑回超市... 这得多累啊!

网页应用也是同样的道理。合理的数据存储就像你的购物清单,把需要的东西记下来,随用随取,效率直接翻倍。

先来看看我们最常用的数据获取方式——Fetch API

Fetch API:现代前端的数据搬运工

Fetch API 是现在最主流的数据请求方式,比老旧的 XMLHttpRequest 好用太多了。它基于 Promise,写起来特别优雅。

1// 最基本的 GET 请求
2async function fetchUserData(userId) {
3  try {
4    // 发起请求,等待响应
5    const response = await fetch([`https://api.example.com/users/${userId}`](https://api.example.com/users/${userId}));
6    
7    // 检查响应是否成功(状态码 200-299)
8    if (!response.ok) {
9      throw new Error(`HTTP error! status: ${response.status}`);
10    }
11    
12    // 解析 JSON 数据
13    const userData = await response.json();
14    return userData;
15  } catch (error) {
16    // 统一的错误处理
17    console.error('获取用户数据失败:', error);
18    throw error;
19  }
20}
21
22// 使用示例
23fetchUserData(123)
24  .then(user => {
25    console.log('用户信息:', user);
26    // 在这里更新页面显示
27  })
28  .catch(error => {
29    // 显示错误提示给用户
30    alert('加载用户信息失败,请重试');
31  });
32

但光会请求数据还不够,聪明的开发者都知道:好的数据要懂得缓存。这就引出了我们的主角——本地存储。

本地存储三剑客:sessionStorage、localStorage、IndexedDB

1. sessionStorage:短暂的记忆

sessionStorage 就像你的短期记忆,页面会话结束时数据就清空了。适合存储一些临时数据。

1// 保存表单草稿
2function saveFormDraft(formData) {
3  // 将对象转换为 JSON 字符串存储
4  sessionStorage.setItem('formDraft', JSON.stringify(formData));
5  console.log('表单草稿已保存');
6}
7
8// 读取表单草稿
9function loadFormDraft() {
10  const draft = sessionStorage.getItem('formDraft');
11  if (draft) {
12    // 将 JSON 字符串解析回对象
13    return JSON.parse(draft);
14  }
15  return null;
16}
17
18// 清除草稿
19function clearFormDraft() {
20  sessionStorage.removeItem('formDraft');
21  console.log('表单草稿已清除');
22}
23
24// 使用示例:页面加载时恢复草稿
25window.addEventListener('load', () => {
26  const draft = loadFormDraft();
27  if (draft) {
28    // 用草稿数据填充表单
29    document.getElementById('username').value = draft.username || '';
30    document.getElementById('email').value = draft.email || '';
31    console.log('表单草稿已恢复');
32  }
33});
34
35// 输入时实时保存
36document.getElementById('myForm').addEventListener('input', (event) => {
37  const formData = {
38    username: document.getElementById('username').value,
39    email: document.getElementById('email').value
40  };
41  saveFormDraft(formData);
42});
43

2. localStorage:持久的仓库

localStorage 是长期存储,除非主动清除,否则数据会一直存在。适合存储用户偏好设置等。

1// 用户主题偏好管理
2class ThemeManager {
3  constructor() {
4    this.currentTheme = this.getSavedTheme() || 'light';
5    this.applyTheme(this.currentTheme);
6  }
7  
8  // 获取保存的主题
9  getSavedTheme() {
10    return localStorage.getItem('userTheme');
11  }
12  
13  // 保存主题偏好
14  saveTheme(theme) {
15    localStorage.setItem('userTheme', theme);
16    this.currentTheme = theme;
17    console.log(`主题已保存: ${theme}`);
18  }
19  
20  // 应用主题
21  applyTheme(theme) {
22    document.documentElement.setAttribute('data-theme', theme);
23    this.saveTheme(theme);
24  }
25  
26  // 切换主题
27  toggleTheme() {
28    const newTheme = this.currentTheme === 'light' ? 'dark' : 'light';
29    this.applyTheme(newTheme);
30  }
31  
32  // 清除主题设置
33  clearTheme() {
34    localStorage.removeItem('userTheme');
35    this.currentTheme = 'light';
36    this.applyTheme('light');
37    console.log('主题设置已清除');
38  }
39}
40
41// 使用示例
42const themeManager = new ThemeManager();
43
44// 主题切换按钮
45document.getElementById('themeToggle').addEventListener('click', () => {
46  themeManager.toggleTheme();
47});
48

3. IndexedDB:大数据专家

当你的数据量很大,或者需要复杂查询时,IndexedDB 就是最佳选择。

1// 创建一个简单的数据库管理器
2class DBManager {
3  constructor(dbName, version) {
4    this.dbName = dbName;
5    this.version = version;
6    this.db = null;
7  }
8  
9  // 打开数据库
10  async open() {
11    return new Promise((resolve, reject) => {
12      const request = indexedDB.open(this.dbName, this.version);
13      
14      request.onerror = () => reject(request.error);
15      request.onsuccess = () => {
16        this.db = request.result;
17        resolve(this.db);
18      };
19      
20      // 第一次创建数据库时初始化结构
21      request.onupgradeneeded = (event) => {
22        const db = event.target.result;
23        
24        // 创建用户表
25        if (!db.objectStoreNames.contains('users')) {
26          const store = db.createObjectStore('users', { keyPath: 'id' });
27          // 创建索引,方便按姓名搜索
28          store.createIndex('name', 'name', { unique: false });
29        }
30        
31        // 创建文章表
32        if (!db.objectStoreNames.contains('articles')) {
33          const store = db.createObjectStore('articles', { keyPath: 'id' });
34          store.createIndex('title', 'title', { unique: false });
35          store.createIndex('createdAt', 'createdAt', { unique: false });
36        }
37      };
38    });
39  }
40  
41  // 添加数据
42  async add(storeName, data) {
43    const transaction = this.db.transaction([storeName], 'readwrite');
44    const store = transaction.objectStore(storeName);
45    
46    return new Promise((resolve, reject) => {
47      const request = store.add(data);
48      request.onerror = () => reject(request.error);
49      request.onsuccess = () => resolve(request.result);
50    });
51  }
52  
53  // 获取所有数据
54  async getAll(storeName) {
55    const transaction = this.db.transaction([storeName], 'readonly');
56    const store = transaction.objectStore(storeName);
57    
58    return new Promise((resolve, reject) => {
59      const request = store.getAll();
60      request.onerror = () => reject(request.error);
61      request.onsuccess = () => resolve(request.result);
62    });
63  }
64  
65  // 按索引查询
66  async getByIndex(storeName, indexName, value) {
67    const transaction = this.db.transaction([storeName], 'readonly');
68    const store = transaction.objectStore(storeName);
69    const index = store.index(indexName);
70    
71    return new Promise((resolve, reject) => {
72      const request = index.getAll(value);
73      request.onerror = () => reject(request.error);
74      request.onsuccess = () => resolve(request.result);
75    });
76  }
77}
78
79// 使用示例
80async function initDB() {
81  const dbManager = new DBManager('MyAppDB', 1);
82  await dbManager.open();
83  
84  // 添加示例用户
85  await dbManager.add('users', {
86    id: 1,
87    name: '张三',
88    email: '[email protected]',
89    createdAt: new Date()
90  });
91  
92  // 获取所有用户
93  const users = await dbManager.getAll('users');
94  console.log('所有用户:', users);
95  
96  return dbManager;
97}
98
99// 初始化数据库
100initDB().then(dbManager => {
101  console.log('数据库初始化完成');
102});
103

实战:构建智能数据缓存系统

现在让我们把 Fetch API 和本地存储结合起来,打造一个真正智能的数据缓存系统。

1// 智能数据管理器
2class SmartDataManager {
3  constructor() {
4    this.cache = new Map(); // 内存缓存
5  }
6  
7  // 获取数据(带缓存)
8  async getData(url, options = {}) {
9    const {
10      cacheKey = url,           // 缓存键名
11      cacheTime = 5 * 60 * 1000, // 默认缓存5分钟
12      forceRefresh = false      // 强制刷新
13    } = options;
14    
15    // 检查内存缓存
16    if (!forceRefresh) {
17      const cached = this.getFromCache(cacheKey, cacheTime);
18      if (cached) {
19        console.log('从内存缓存返回数据');
20        return cached;
21      }
22      
23      // 检查 localStorage 缓存
24      const stored = this.getFromStorage(cacheKey, cacheTime);
25      if (stored) {
26        console.log('从本地存储返回数据');
27        // 同时更新内存缓存
28        this.cache.set(cacheKey, {
29          data: stored,
30          timestamp: Date.now()
31        });
32        return stored;
33      }
34    }
35    
36    // 缓存中没有,从接口获取
37    console.log('从接口获取数据');
38    try {
39      const response = await fetch(url);
40      if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
41      
42      const data = await response.json();
43      
44      // 同时更新内存缓存和本地存储
45      this.setCache(cacheKey, data);
46      this.setStorage(cacheKey, data);
47      
48      return data;
49    } catch (error) {
50      console.error('获取数据失败:', error);
51      throw error;
52    }
53  }
54  
55  // 从内存缓存获取
56  getFromCache(key, cacheTime) {
57    const cached = this.cache.get(key);
58    if (cached && Date.now() - cached.timestamp < cacheTime) {
59      return cached.data;
60    }
61    return null;
62  }
63  
64  // 从本地存储获取
65  getFromStorage(key, cacheTime) {
66    try {
67      const stored = localStorage.getItem(`cache_${key}`);
68      if (stored) {
69        const { data, timestamp } = JSON.parse(stored);
70        if (Date.now() - timestamp < cacheTime) {
71          return data;
72        } else {
73          // 缓存过期,清理
74          localStorage.removeItem(`cache_${key}`);
75        }
76      }
77    } catch (error) {
78      console.warn('读取缓存失败:', error);
79    }
80    return null;
81  }
82  
83  // 设置内存缓存
84  setCache(key, data) {
85    this.cache.set(key, {
86      data,
87      timestamp: Date.now()
88    });
89  }
90  
91  // 设置本地存储
92  setStorage(key, data) {
93    try {
94      localStorage.setItem(`cache_${key}`, JSON.stringify({
95        data,
96        timestamp: Date.now()
97      }));
98    } catch (error) {
99      console.warn('存储缓存失败:', error);
100      // 如果存储失败(比如超出容量),清理最旧的缓存
101      this.cleanupStorage();
102    }
103  }
104  
105  // 清理过期缓存
106  cleanupStorage() {
107    const keysToRemove = [];
108    
109    for (let i = 0; i < localStorage.length; i++) {
110      const key = localStorage.key(i);
111      if (key.startsWith('cache_')) {
112        try {
113          const stored = JSON.parse(localStorage.getItem(key));
114          // 删除超过1天的缓存
115          if (Date.now() - stored.timestamp > 24 * 60 * 60 * 1000) {
116            keysToRemove.push(key);
117          }
118        } catch (error) {
119          // 数据格式错误,直接删除
120          keysToRemove.push(key);
121        }
122      }
123    }
124    
125    keysToRemove.forEach(key => localStorage.removeItem(key));
126  }
127  
128  // 清除指定缓存
129  clearCache(key) {
130    this.cache.delete(key);
131    localStorage.removeItem(`cache_${key}`);
132  }
133  
134  // 清除所有缓存
135  clearAllCache() {
136    this.cache.clear();
137    Object.keys(localStorage)
138      .filter(key => key.startsWith('cache_'))
139      .forEach(key => localStorage.removeItem(key));
140  }
141}
142
143// 使用示例
144const dataManager = new SmartDataManager();
145
146// 获取用户列表(带缓存)
147async function loadUsers() {
148  try {
149    const users = await dataManager.getData('/api/users', {
150      cacheKey: 'user_list',
151      cacheTime: 10 * 60 * 1000 // 缓存10分钟
152    });
153    
154    // 渲染用户列表
155    renderUserList(users);
156  } catch (error) {
157    // 显示错误状态
158    showError('加载用户列表失败');
159  }
160}
161
162// 强制刷新数据
163async function refreshUsers() {
164  try {
165    const users = await dataManager.getData('/api/users', {
166      cacheKey: 'user_list',
167      forceRefresh: true // 强制从接口获取最新数据
168    });
169    
170    renderUserList(users);
171    showSuccess('数据已刷新');
172  } catch (error) {
173    showError('刷新数据失败');
174  }
175}
176

离线优先:打造极致用户体验

现代 Web 应用应该具备离线能力,让用户在网络不稳定时也能正常使用。

1// 离线优先的数据同步器
2class OfflineFirstSync {
3  constructor() {
4    this.dbManager = null;
5    this.pendingSync = []; // 待同步的操作
6    this.init();
7  }
8  
9  async init() {
10    // 初始化 IndexedDB
11    this.dbManager = new DBManager('OfflineApp', 1);
12    await this.dbManager.open();
13    
14    // 监听网络状态
15    this.setupNetworkListener();
16    
17    // 尝试同步待处理的操作
18    this.trySyncPending();
19  }
20  
21  // 设置网络状态监听
22  setupNetworkListener() {
23    window.addEventListener('online', () => {
24      console.log('网络已连接,开始同步数据...');
25      this.trySyncPending();
26    });
27    
28    window.addEventListener('offline', () => {
29      console.log('网络已断开,进入离线模式');
30      this.showOfflineIndicator();
31    });
32  }
33  
34  // 创建数据(离线优先)
35  async createData(storeName, data) {
36    // 先保存到本地数据库
37    const localId = await this.dbManager.add(storeName, {
38      ...data,
39      _local: true,    // 标记为本地创建
40      _synced: false,  // 未同步
41      _createdAt: new Date()
42    });
43    
44    // 添加到待同步队列
45    this.pendingSync.push({
46      type: 'create',
47      storeName,
48      data: { ...data, _localId: localId }
49    });
50    
51    // 尝试立即同步
52    await this.trySyncPending();
53    
54    return localId;
55  }
56  
57  // 尝试同步待处理操作
58  async trySyncPending() {
59    if (!navigator.onLine || this.pendingSync.length === 0) {
60      return;
61    }
62    
63    console.log(`开始同步 ${this.pendingSync.length} 个操作`);
64    
65    const successes = [];
66    const failures = [];
67    
68    for (const operation of [...this.pendingSync]) {
69      try {
70        await this.syncOperation(operation);
71        successes.push(operation);
72        
73        // 从待同步队列中移除成功的操作
74        const index = this.pendingSync.indexOf(operation);
75        if (index > -1) {
76          this.pendingSync.splice(index, 1);
77        }
78      } catch (error) {
79        console.error('同步操作失败:', error);
80        failures.push(operation);
81      }
82    }
83    
84    if (successes.length > 0) {
85      console.log(`成功同步 ${successes.length} 个操作`);
86      this.showSyncSuccess(successes.length);
87    }
88    
89    if (failures.length > 0) {
90      console.warn(`${failures.length} 个操作同步失败,将在下次重试`);
91    }
92  }
93  
94  // 同步单个操作
95  async syncOperation(operation) {
96    switch (operation.type) {
97      case 'create':
98        // 调用 API 创建数据
99        const response = await fetch('/api/' + operation.storeName, {
100          method: 'POST',
101          headers: {
102            'Content-Type': 'application/json'
103          },
104          body: JSON.stringify(operation.data)
105        });
106        
107        if (!response.ok) {
108          throw new Error(`创建失败: ${response.status}`);
109        }
110        
111        const result = await response.json();
112        
113        // 更新本地数据,标记为已同步
114        // 这里可以根据需要更新本地记录的ID等
115        console.log('数据同步成功:', result);
116        break;
117        
118      default:
119        console.warn('未知的操作类型:', operation.type);
120    }
121  }
122  
123  // 显示离线指示器
124  showOfflineIndicator() {
125    // 在实际应用中,可以显示一个离线提示条
126    const indicator = document.createElement('div');
127    indicator.style.cssText = `
128      position: fixed;
129      top: 0;
130      left: 0;
131      right: 0;
132      background: #ff6b6b;
133      color: white;
134      text-align: center;
135      padding: 10px;
136      z-index: 1000;
137    `;
138    indicator.textContent = '当前处于离线模式,部分功能可能受限';
139    indicator.id = 'offline-indicator';
140    
141    document.body.appendChild(indicator);
142  }
143  
144  // 显示同步成功提示
145  showSyncSuccess(count) {
146    const indicator = document.getElementById('offline-indicator');
147    if (indicator) {
148      indicator.remove();
149    }
150    
151    // 显示同步成功提示(可以替换为更优雅的通知)
152    console.log(`成功同步 ${count} 条数据`);
153  }
154  
155  // 获取数据(离线优先)
156  async getData(storeName, useLocalFirst = true) {
157    if (useLocalFirst) {
158      // 先返回本地数据
159      const localData = await this.dbManager.getAll(storeName);
160      
161      // 同时在后台尝试获取最新数据
162      this.fetchLatestData(storeName);
163      
164      return localData;
165    } else {
166      // 直接获取最新数据
167      return await this.fetchLatestData(storeName);
168    }
169  }
170  
171  // 获取最新数据
172  async fetchLatestData(storeName) {
173    if (!navigator.onLine) {
174      throw new Error('网络不可用');
175    }
176    
177    try {
178      const response = await fetch(`/api/${storeName}`);
179      if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
180      
181      const data = await response.json();
182      
183      // 更新本地数据库
184      // 这里需要根据具体业务逻辑实现数据合并
185      console.log('获取到最新数据:', data);
186      
187      return data;
188    } catch (error) {
189      console.error('获取最新数据失败:', error);
190      throw error;
191    }
192  }
193}
194
195// 使用示例
196const offlineSync = new OfflineFirstSync();
197
198// 在离线状态下创建用户
199async function createUserOffline(userData) {
200  try {
201    const localId = await offlineSync.createData('users', userData);
202    console.log('用户已创建(本地):', localId);
203    showSuccess('用户已保存,将在网络恢复后同步');
204  } catch (error) {
205    console.error('创建用户失败:', error);
206    showError('保存用户失败');
207  }
208}
209

性能优化与最佳实践

掌握了基础用法,再来看看一些提升性能的实用技巧。

1// 防抖请求,避免频繁调用接口
2function createDebouncedFetcher(delay = 500) {
3  let timeoutId;
4  
5  return async function debouncedFetch(url, options) {
6    // 清除之前的定时器
7    if (timeoutId) {
8      clearTimeout(timeoutId);
9    }
10    
11    // 设置新的定时器
12    return new Promise((resolve, reject) => {
13      timeoutId = setTimeout(async () => {
14        try {
15          const response = await fetch(url, options);
16          const data = await response.json();
17          resolve(data);
18        } catch (error) {
19          reject(error);
20        }
21      }, delay);
22    });
23  };
24}
25
26// 使用防抖的搜索功能
27const debouncedSearch = createDebouncedFetcher(300);
28
29document.getElementById('searchInput').addEventListener('input', async (event) => {
30  const query = event.target.value.trim();
31  
32  if (query.length < 2) {
33    // 清空搜索结果
34    clearSearchResults();
35    return;
36  }
37  
38  try {
39    const results = await debouncedSearch([`/api/search?q=${encodeURIComponent(query)}`](https://xplanc.org/primers/document/zh/03.HTML/EX.HTML%20%E5%85%83%E7%B4%A0/EX.q.md));
40    displaySearchResults(results);
41  } catch (error) {
42    console.error('搜索失败:', error);
43    // 可以显示本地缓存的结果或错误提示
44  }
45});
46
47// 批量操作优化
48async function batchOperations(operations, batchSize = 5) {
49  const results = [];
50  
51  for (let i = 0; i < operations.length; i += batchSize) {
52    const batch = operations.slice(i, i + batchSize);
53    
54    // 并行执行批次内的操作
55    const batchResults = await Promise.allSettled(
56      batch.map(op => executeOperation(op))
57    );
58    
59    results.push(...batchResults);
60    
61    // 可选:批次间延迟,避免对服务器造成太大压力
62    if (i + batchSize < operations.length) {
63      await new Promise(resolve => setTimeout(resolve, 100));
64    }
65  }
66  
67  return results;
68}
69
70// 数据压缩,减少存储空间
71function compressData(data) {
72  // 简单的数据压缩示例
73  const compressed = {
74    // 移除空值
75    ...Object.fromEntries(
76      Object.entries(data).filter(([_, value]) => 
77        value !== null && value !== undefined && value !== ''
78      )
79    ),
80    // 添加压缩标记
81    _compressed: true
82  };
83  
84  return compressed;
85}
86
87// 数据解压缩
88function decompressData(compressedData) {
89  const { _compressed, ...data } = compressedData;
90  return data;
91}
92
93// 使用压缩存储
94function saveCompressedData(key, data) {
95  const compressed = compressData(data);
96  localStorage.setItem(key, JSON.stringify(compressed));
97}
98
99function loadCompressedData(key) {
100  const stored = localStorage.getItem(key);
101  if (stored) {
102    const compressed = JSON.parse(stored);
103    return decompressData(compressed);
104  }
105  return null;
106}
107

错误处理与监控

健壮的应用离不开完善的错误处理。

1// 增强的错误处理包装器
2function createRobustFetcher(options = {}) {
3  const {
4    maxRetries = 3,
5    retryDelay = 1000,
6    timeout = 10000
7  } = options;
8  
9  return async function robustFetch(url, fetchOptions = {}) {
10    let lastError;
11    
12    for (let attempt = 1; attempt <= maxRetries; attempt++) {
13      try {
14        // 创建超时控制器
15        const controller = new AbortController();
16        const timeoutId = setTimeout(() => controller.abort(), timeout);
17        
18        const response = await fetch(url, {
19          ...fetchOptions,
20          signal: controller.signal
21        });
22        
23        clearTimeout(timeoutId);
24        
25        if (!response.ok) {
26          throw new Error(`HTTP error! status: ${response.status}`);
27        }
28        
29        return await response.json();
30        
31      } catch (error) {
32        lastError = error;
33        
34        console.warn(`请求失败 (尝试 ${attempt}/${maxRetries}):`, error);
35        
36        if (attempt < maxRetries) {
37          // 指数退避延迟
38          const delay = retryDelay * Math.pow(2, attempt - 1);
39          console.log(`等待 ${delay}ms 后重试...`);
40          await new Promise(resolve => setTimeout(resolve, delay));
41        }
42      }
43    }
44    
45    // 所有重试都失败了
46    throw new Error(`请求失败,已重试 ${maxRetries} 次: ${lastError.message}`);
47  };
48}
49
50// 使用增强的请求器
51const robustFetch = createRobustFetcher({
52  maxRetries: 3,
53  retryDelay: 1000,
54  timeout: 15000
55});
56
57// 数据健康检查
58class DataHealthChecker {
59  static checkLocalStorage() {
60    const issues = [];
61    
62    try {
63      // 测试写入和读取
64      const testKey = '__health_check__';
65      const testValue = { timestamp: Date.now() };
66      
67      localStorage.setItem(testKey, JSON.stringify(testValue));
68      const retrieved = JSON.parse(localStorage.getItem(testKey));
69      localStorage.removeItem(testKey);
70      
71      if (!retrieved || retrieved.timestamp !== testValue.timestamp) {
72        issues.push('localStorage 数据完整性检查失败');
73      }
74    } catch (error) {
75      issues.push(`localStorage 不可用: ${error.message}`);
76    }
77    
78    return issues;
79  }
80  
81  static checkIndexedDB() {
82    return new Promise((resolve) => {
83      const issues = [];
84      
85      const request = indexedDB.open('health_check', 1);
86      request.onerror = () => {
87        issues.push('IndexedDB 无法打开');
88        resolve(issues);
89      };
90      
91      request.onsuccess = () => {
92        const db = request.result;
93        db.close();
94        
95        // 清理测试数据库
96        indexedDB.deleteDatabase('health_check');
97        resolve(issues);
98      };
99      
100      request.onblocked = () => {
101        issues.push('IndexedDB 被阻塞');
102        resolve(issues);
103      };
104    });
105  }
106  
107  static async runAllChecks() {
108    const localStorageIssues = this.checkLocalStorage();
109    const indexedDBIssues = await this.checkIndexedDB();
110    
111    const allIssues = [...localStorageIssues, ...indexedDBIssues];
112    
113    if (allIssues.length === 0) {
114      console.log('✅ 所有存储系统正常');
115    } else {
116      console.warn('❌ 存储系统问题:', allIssues);
117    }
118    
119    return allIssues;
120  }
121}
122
123// 定期运行健康检查
124setInterval(async () => {
125  await DataHealthChecker.runAllChecks();
126}, 5 * 60 * 1000); // 每5分钟检查一次
127

总结

通过今天的学习,相信你已经掌握了:

✅ Fetch API 的现代用法和错误处理 ✅ 三种本地存储方案的适用场景 ✅ 如何构建智能缓存系统提升性能 ✅ 离线优先的设计思路 ✅ 各种性能优化和监控技巧

数据交互不再是简单的"请求-显示",而是要考虑缓存、离线、同步、性能等方方面面。一个好的数据层设计,能让你的应用用户体验提升好几个档次。


前端别再乱存数据了!这3种存储方案让你的应用快如闪电》 是转载文章,点击查看原文


相关推荐


Bash 的变量
hubenchang05152025/10/25

#Bash 的变量 Bash 中的变量定义语法如下: 变量名=值 注意,等号(=)两边不能有空格。 变量名的命名应当遵循如下规则: 只能包含字母,数字和下划线(_),并且不能以数字开头。 不能使用 Bash 保留的关键字,如:if then else fi for while do done 等 环境变量和常量使用全大写字母,单词间使用下划线分隔 普通变量使用全小写字母,单词间使用下划线分隔 函数内的局部变量使用 local 关键字声明 例如: PI=3.1415925 URL="htt


搜索百科(5):Easysearch — 自主可控的国产分布式搜索引擎
极限实验室2025/10/23

大家好,我是 INFINI Labs 的石阳。 欢迎关注 《搜索百科》 专栏!每天 5 分钟,带你速览一款搜索相关的技术或产品,同时还会带你探索它们背后的技术原理、发展故事及上手体验等。 在上一篇我们介绍了 OpenSearch —— 那个因协议争议而诞生的开源搜索分支。今天,我们把目光转向国内,聊聊极限科技研发的一款轻量级搜索引擎:Easysearch。 引言 在搜索技术的世界里,从 Lucene 的出现到 Solr、Elasticsearch 的崛起,搜索引擎技术已经发展了二十余年。然而,随


Swift 字符串与字符完全导读(一):从字面量到 Unicode 的实战之旅
unravel20252025/10/22

前言 Swift 的 String 看起来“像 NSString 的弟弟”,但骨子里是一套全新的 Unicode 抽象模型。 String 与 Character 的本质 String:由“扩展字形簇”(extended grapheme cluster)构成的有序集合。 Character:一个扩展字形簇,人类眼中的“一个字符”,占用的字节数可变。 // 1 个 Character,由 2 个 Unicode 标量合成 let eAcute: Character = "é"


JAVA面试复习笔记(待完善)
paishishaba2025/10/20

目录 布隆过滤器 一、核心思想 二、执行逻辑详解 1. 添加元素 2. 查询元素 三、为什么会有误判? 四、关键参数与性能权衡 五、执行逻辑总结与特点 六、典型应用场景 Redis 的 SETNX 命令 一、基本语法和语义 二、简单示例 三、SETNX 的核心特性 1. 原子性 2. 简单性 3. 无过期时间 四、经典应用场景 1. 分布式锁(最经典的应用) 五、SETNX 的局限性及改进方案 问题1:非原子性的设置过期时间 解决方案:使用 SET 命令


Windows Server,如何使用WSFC+nginx实现集群故障转移
IT橘子皮2025/10/19

在 Windows Server 环境中结合 WSFC(Windows Server Failover Clustering)和 Nginx 实现集群故障转移,核心目标是构建一个既具备应用层高可用性(由 Nginx 负责),又具备基础设施层高可用性(由 WSFC 保障 Nginx 服务本身)的稳固架构。下面这张图清晰地展示了这套架构的完整工作流程: 上图展示了WSFC如何通过心跳检测监控Nginx主节点的状态,并在故障发生时自动将服务(包括虚拟IP和Nginx进程)转移到备节点。下面我们详细拆


AI修图革命:IOPaint+cpolar让废片拯救触手可及
倔强的石头_2025/10/18

文章目录 前言【视频教程】1.什么是IOPaint?2.本地部署IOPaint3.IOPaint简单实用4.公网远程访问本地IOPaint5.内网穿透工具安装6.配置公网地址7.使用固定公网地址远程访问总结 前言 旅行拍照时意外拍到路人闯入?证件照背景不合规?传统修图软件学习成本高,在线工具又担心隐私泄露?IOPaint的出现给出了完美解方——这款开源AI修图工具支持一键擦除多余物体、修复老照片瑕疵,所有操作在本地完成,无需上传原始图片。特别适合摄影爱好者和自媒体创作者,其


“签名”这个概念是非对称加密独有的吗?
你的人类朋友2025/10/16

前言 🍃 你好啊,我是你的人类朋友 ☺️ 本篇文章主要来自于我之前在工作中犯的一个对“签名”的概念的误解 问大家一个问题: "签名"这个概念是非对称加密独有的吗? 先说答案:不是。 虽然【数字签名】确实是非对称加密技术的重要应用,但【"签名"】这个概念在密码学中有着更广泛的含义和应用。 今天就来澄清这个误解,也是我之前在学习信息安全的过程中犯的一个错误 🙀。 正文 一、签名的本质目的 在密码学中,"签名"的核心目的是提供: 身份认证 - 证明消息确实来自声称的发送方 完整性验证 - 确保消


Redis(63)Redis的Lua脚本如何使用?
Victor3562025/10/15

Redis 提供了对 Lua 脚本的支持,使得用户可以在服务器端执行原子操作,从而能够降低网络开销、提高性能和确保操作的原子性。以下是关于如何使用 Redis 的 Lua 脚本的详细指南,并结合实例代码进行说明。 1. 使用 Lua 脚本的基本命令 Redis 提供了两个主要命令来执行 Lua 脚本: EVAL EVALSHA EVAL 命令 EVAL 命令用于直接执行 Lua 脚本。 语法: EVAL script numkeys key [key ...] arg [arg ...]


html5的新增标签
期待~明天2025/10/14

HTML5新增结构标签 HTML5引入了多个语义化标签,用于更清晰地描述网页结构: <header>:定义文档或节的页眉<footer>:定义文档或节的页脚<nav>:定义导航链接<article>:定义独立的内容块<section>:定义文档中的节<aside>:定义页面内容之外的内容(如侧栏) HTML5新增多媒体标签 为支持原生多媒体内容,新增以下标签: <audio>:嵌入音频内容<video>:嵌入视频内容<source>:为多媒体元素定义多种媒体资源<track>:为视频添


在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
IT橘子皮2025/10/12

在高可用架构中,Spring Cloud Gateway 集群的设计是关键环节。下面我将从核心架构、关键配置到生产级优化,为你梳理高可用网关集群的设计方案。 🏗️ 集群架构设计 构建高可用网关集群的核心是消除单点故障,并通过分层设计实现流量可靠分发。 ​核心模式​:标准的解决方案是部署多个 Gateway 实例,并在前端配置一个负载均衡器(如云服务商的SLB/ELB/ALB或自建的Nginx/HAProxy)作为统一的流量入口。所有外部请求先到达负载均衡器,再由其分发到后端的各个Gatewa

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0