你是不是也遇到过这样的场景?
用户刚填完一个超长的表单,不小心刷新了页面,所有数据都没了... 从接口请求的数据,用户每次操作都要重新加载,体验卡成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种存储方案让你的应用快如闪电》 是转载文章,点击查看原文。
