当前位置: 首页 > news >正文

JavaScript性能优化实战(8):缓存策略与离线优化

前言

在Web应用中,性能优化不仅仅是关于代码执行速度,还与资源获取和数据持久化密切相关。合理的缓存策略可以显著减少网络请求,提升应用响应速度,同时有效降低服务器负载和用户流量消耗。离线优化则进一步解决了网络不稳定或断网场景下的用户体验问题,为Web应用提供类似原生应用的可靠性。

本文作为JavaScript性能优化实战系列的第八篇,将深入探讨前端缓存策略与离线优化技术。我们将从浏览器原生缓存机制出发,逐步深入到Service Worker、IndexedDB等现代Web API,并探讨PWA离线体验优化的实战应用,最终构建离线优先的Web应用架构。

浏览器缓存机制全面解析

浏览器缓存是前端性能优化的第一道防线,合理利用浏览器缓存机制可以大幅减少网络请求,加快页面加载速度。

HTTP缓存基础

HTTP缓存是最基础的缓存形式,通过HTTP头部控制资源的缓存行为。

强缓存

强缓存是指在缓存期间不需要请求服务器,直接使用缓存的资源。主要通过以下HTTP头部控制:

  1. Cache-Control:HTTP/1.1的缓存控制头,优先级高于Expires

    • max-age:缓存有效期(秒)
    • public:可以被任何缓存区缓存
    • private:只能被浏览器缓存
    • no-cache:每次使用缓存前必须先验证资源是否有效
    • no-store:完全不使用缓存
  2. Expires:HTTP/1.0的缓存控制头,指定资源过期的具体时间

// 强缓存示例响应头
Cache-Control: max-age=3600, public
Expires: Wed, 21 Oct 2023 07:28:00 GMT

当浏览器发起请求时,会先检查是否命中强缓存:

  • 如果命中,直接从缓存读取资源,此时Chrome开发者工具的Network面板会显示(from disk cache)(from memory cache)
  • 如果未命中,则进入协商缓存环节
协商缓存

协商缓存是指浏览器需要向服务器发送请求以确认缓存是否有效。主要通过以下HTTP头部控制:

  1. ETag/If-None-Match:资源的唯一标识符

    • 服务器返回资源时设置ETag
    • 浏览器请求时在If-None-Match中带上上次响应的ETag值
  2. Last-Modified/If-Modified-Since:资源的最后修改时间

    • 服务器返回资源时设置Last-Modified
    • 浏览器请求时在If-Modified-Since中带上上次响应的Last-Modified值
// 协商缓存示例
// 服务器响应头
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2023 07:28:00 GMT// 浏览器后续请求头
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
If-Modified-Since: Wed, 21 Oct 2023 07:28:00 GMT

协商缓存的工作流程:

  • 浏览器发送带有缓存校验字段的请求
  • 服务器判断缓存是否有效
    • 有效:返回304 Not Modified响应,无响应体
    • 无效:返回200 OK响应,带有新的资源
完整的缓存判断流程

下面是一个完整的浏览器缓存判断流程:

发起请求 ---> 是否有缓存? ---> 否 ---> 向服务器请求 --> 返回200,缓存资源|是|强缓存是否有效? ---> 是 ---> 使用缓存,不发送请求 (200 from cache)|否|发送协商缓存请求 ---> 服务器判断缓存是否有效|是|返回304,使用缓存|否|返回200,更新缓存

常见资源类型的缓存策略

不同类型的资源适合不同的缓存策略:

HTML文档

HTML文档通常包含应用的结构和入口,建议使用协商缓存而非强缓存,确保用户能及时获取最新内容:

Cache-Control: no-cache
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
JavaScript和CSS文件

对于带有版本号或哈希的静态资源,可以使用长期的强缓存:

Cache-Control: max-age=31536000, public

在构建系统中可以这样实现:

// webpack配置示例
module.exports = {output: {filename: '[name].[contenthash].js',chunkFilename: '[name].[contenthash].chunk.js'}
};
图片和媒体文件

图片和媒体文件通常变化不频繁,适合使用长期缓存:

Cache-Control: max-age=86400, public
API响应数据

API响应通常是动态数据,不应该被强缓存,但可以根据业务需求使用短期缓存:

Cache-Control: max-age=60, private

对于不应该缓存的敏感数据:

Cache-Control: no-store

缓存控制最佳实践

版本化静态资源

对静态资源使用内容哈希作为文件名的一部分,确保内容变化时URL也随之变化:

// 在构建工具中的配置
{output: {// 使用contenthash确保内容变化时文件名变化filename: '[name].[contenthash:8].js',// 其他资源也使用类似策略assetModuleFilename: 'assets/[name].[contenthash:8][ext]'}
}
合理设置Cache-Control

根据资源类型和更新频率设置适当的Cache-Control:

// Node.js服务器示例
app.use('/static', express.static('public', {etag: true,lastModified: true,setHeaders: (res, path) => {if (path.endsWith('.html')) {// HTML文件使用协商缓存res.setHeader('Cache-Control', 'no-cache');} else if (path.match(/\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$/)) {// 带版本号的静态资源使用长期缓存res.setHeader('Cache-Control', 'max-age=31536000, public');}}
}));
使用Vary头处理不同的客户端请求

当响应内容根据请求头变化时,应使用Vary头指定这些头部:

Vary: User-Agent, Accept-Encoding

这确保缓存能正确区分不同客户端或不同压缩格式的响应。

避免缓存私人信息

对于包含用户私人信息的响应,应明确防止缓存:

Cache-Control: private, no-store, max-age=0

实战案例:构建高效的缓存系统

前端缓存层设计

以下是一个典型的前端应用缓存层设计:

[用户请求] --> [Service Worker缓存] --> [HTTP缓存] --> [服务器]|[浏览器存储](IndexedDB/localStorage)
实现多级缓存策略

下面是一个结合HTTP缓存和前端存储的多级缓存实现:

// 封装一个带缓存的API请求函数
async function fetchWithCache(url, options = {}) {const cacheKey = `api:${url}`;const cacheTime = options.cacheTime || 60000; // 默认缓存1分钟// 尝试从localStorage获取缓存try {const cached = localStorage.getItem(cacheKey);if (cached) {const { timestamp, data } = JSON.parse(cached);// 检查缓存是否有效if (Date.now() - timestamp < cacheTime) {console.log('从本地缓存获取数据:', url);return data;}}} catch (e) {console.error('读取缓存出错:', e);}// 发起网络请求,使用HTTP缓存try {const response = await fetch(url, {...options,headers: {...options.headers,// 对于GET请求可以使用协商缓存...(options.method === 'GET' ? {} : { 'Cache-Control': 'no-cache' })}});if (!response.ok) {throw new Error(`请求失败: ${response.status}`);}const data = await response.json();// 将数据存入localStorage缓存try {localStorage.setItem(cacheKey, JSON.stringify({timestamp: Date.now(),data}));} catch (e) {console.error('写入缓存出错:', e);}return data;} catch (error) {console.error('网络请求失败:', error);throw error;}
}// 使用示例
async function getUserProfile(userId) {return fetchWithCache(`/api/users/${userId}`, { cacheTime: 300000 }); // 缓存5分钟
}async function getProductList() {return fetchWithCache('/api/products', { cacheTime: 60000 }); // 缓存1分钟
}

浏览器缓存查看与调试技巧

使用Chrome DevTools检查缓存
  1. 查看HTTP缓存

    • 打开Chrome DevTools > Network面板
    • 勾选"Disable cache"可临时禁用缓存
    • 查看Size列,显示"(from disk cache)“或”(from memory cache)"表示命中缓存
    • 查看Status列,304表示命中协商缓存
  2. 检查应用缓存存储

    • 打开Chrome DevTools > Application面板
    • 在Storage部分可以查看和管理各种缓存和存储
    • Cache Storage查看Service Worker缓存
    • IndexedDB和Local Storage查看对应存储
缓存调试常用命令
// 清除localStorage缓存
localStorage.clear();// 编程方式检查缓存头
fetch('/example.js').then(response => {console.log('Cache-Control:', response.headers.get('Cache-Control'));console.log('ETag:', response.headers.get('ETag'));console.log('Last-Modified:', response.headers.get('Last-Modified'));});// 使用performance API分析缓存性能
performance.getEntriesByType('resource').forEach(resource => {console.log(`${resource.name}: ${resource.duration}ms`);
});

通过深入理解和合理应用浏览器缓存机制,我们可以显著提升Web应用的性能和用户体验。在下一节中,我们将探讨更强大的Service Worker缓存策略,它不仅能实现更细粒度的缓存控制,还能为Web应用提供真正的离线能力。

Service Worker缓存策略与实现

Service Worker是一种运行在浏览器背后的脚本,它独立于网页,能够拦截和处理网络请求,实现离线缓存、推送通知等功能。相比传统的HTTP缓存,Service Worker提供了更精细的缓存控制能力和离线支持。

Service Worker基础

Service Worker生命周期

Service Worker有着明确的生命周期,理解这一点对于实现可靠的缓存策略至关重要:

  1. 注册(Register):告知浏览器Service Worker的位置
  2. 安装(Install):首次安装或更新时触发,通常用于缓存静态资源
  3. 激活(Activate):安装成功后激活,通常用于清理旧缓存
  4. 空闲(Idle):未处理任何事件时的状态
  5. 终止(Terminated):空闲一段时间后被终止,节省资源
  6. 获取(Fetch):拦截网络请求,可以返回缓存或发起新请求
注册Service Worker

首先需要在主页面注册Service Worker:

// 检查浏览器是否支持Service Worker
if ('serviceWorker' in navigator) {window.addEventListener('load', () => {navigator.serviceWorker.register('/sw.js').then(registration => {console.log('Service Worker注册成功,作用域为:', registration.scope);}).catch(error => {console.error('Service Worker注册失败:', error);});});
}
创建基本的Service Worker

以下是一个基本的Service Worker示例(sw.js):

// 定义缓存名称和缓存资源列表
const CACHE_NAME = 'app-v1';
const CACHE_ASSETS = ['/','/index.html','/css/style.css','/js/main.js','/images/logo.png','/offline.html'
];// 安装事件:预缓存静态资源
self.addEventListener('install', event => {console.log('Service Worker: 安装中');// 延长安装阶段直到缓存完成event.waitUntil(caches.open(CACHE_NAME).then(cache => {console.log('Service Worker: 缓存文件');return cache.addAll(CACHE_ASSETS);}).then(() => self.skipWaiting()) // 强制激活);
});// 激活事件:清理旧缓存
self.addEventListener('activate', event => {console.log('Service Worker: 已激活');event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cache => {if (cache !== CACHE_NAME) {console.log('Service Worker: 清理旧缓存', cache);return caches.delete(cache);}}));}));
});// 拦截fetch请求
self.addEventListener('fetch', event => {console.log('Service Worker: 拦截请求', event.request.url);event.respondWith(// 检查缓存caches.match(event.request).then(response => {// 如果在缓存中找到了响应,返回缓存的版本if (response) {return response;}// 没有缓存,发起网络请求return fetch(event.request).then(response => {// 检查是否得到有效响应if (!response || response.status !== 200 || response.type !== 'basic') {return response;}// 复制响应(因为响应是流,只能消费一次)const responseToCache = response.clone();// 将响应添加到缓存caches.open(CACHE_NAME).then(cache => {cache.put(event.request, responseToCache);});return response;}).catch(() => {// 网络请求失败时,返回离线页面if (event.request.headers.get('accept').includes('text/html')) {return caches.match('/offline.html');}});}));
});

高级缓存策略

Service Worker提供了灵活的缓存策略选择,根据不同资源类型和应用需求,可以实现多种缓存模式:

1. 缓存优先(Cache First)

优先从缓存获取资源,缓存未命中才发起网络请求。适合静态资源和不经常更改的内容。

function cacheFirst(request) {return caches.match(request).then(cacheResponse => {return cacheResponse || fetch(request).then(networkResponse => {// 将网络响应存入缓存return caches.open(CACHE_NAME).then(cache => {cache.put(request, networkResponse.clone());return networkResponse;});});});
}
2. 网络优先(Network First)

优先从网络获取最新资源,网络请求失败才使用缓存。适合经常更新的内容,如API响应。

function networkFirst(request) {return fetch(request).then(networkResponse => {// 将网络响应复制一份存入缓存caches.open(CACHE_NAME).then(cache => {cache.put(request, networkResponse.clone());});return networkResponse;}).catch(() => {// 网络请求失败,使用缓存return caches.match(request);});
}
3. 网络优先并更新缓存(Stale While Revalidate)

同时从缓存和网络获取资源,先返回缓存内容(如果有)以快速响应,同时更新缓存以备下次使用。适合内容更新不那么关键的场景。

function staleWhileRevalidate(request) {return caches.open(CACHE_NAME).then(cache => {return cache.match(request).then(cacheResponse => {const fetchPromise = fetch(request).then(networkResponse => {cache.put(request, networkResponse.clone());return networkResponse;});// 返回缓存响应,或等待网络响应return cacheResponse || fetchPromise;});});
}
4. 缓存与网络竞争(Cache and Network Race)

同时从缓存和网络获取资源,返回最先得到的响应。适合优化在不同网络环境下的体验。

function raceStrategy(request) {// 创建Promise.race,返回最快的响应return Promise.race([caches.match(request).then(response => response),fetch(request).then(response => {// 更新缓存const responseClone = response.clone();caches.open(CACHE_NAME).then(cache => {cache.put(request, responseClone);});return response;})]);
}
5. 缓存回退网络(Cache Falling Back to Network)

先尝试使用缓存,缓存未命中时再发起网络请求。网络请求失败时可以提供通用的离线页面。

function cacheFallbackToNetwork(request) {return caches.match(request).then(cacheResponse => {if (cacheResponse) {return cacheResponse;}return fetch(request).catch(() => {// 请求HTML内容时返回通用离线页面if (request.headers.get('Accept').includes('text/html')) {return caches.match('/offline.html');}// 对于图片可以返回占位图if (request.url.match(/\.(jpg|png|gif|svg)$/)) {return caches.match('/images/offline-image.png');}});});
}

根据资源类型选择缓存策略

在实际应用中,通常需要根据不同的资源类型选择不同的缓存策略:

self.addEventListener('fetch', event => {const request = event.request;const url = new URL(request.url);// 对同源请求应用缓存策略if (url.origin === self.location.origin) {// 静态资源使用缓存优先策略if (url.pathname.startsWith('/static/') || url.pathname.match(/\.(css|js|png|jpg|jpeg|svg|gif)$/)) {event.respondWith(cacheFirst(request));return;}// HTML页面使用网络优先策略if (request.headers.get('Accept').includes('text/html')) {event.respondWith(networkFirst(request));return;}}// API请求使用staleWhileRevalidate策略if (url.pathname.startsWith('/api/')) {event.respondWith(staleWhileRevalidate(request));return;}// 默认使用网络优先策略event.respondWith(networkFirst(request));
});

定期更新缓存内容

有些情况下,我们需要定期更新缓存内容,而不仅依赖于用户的请求:

// 定义需要定期更新的资源
const PERIODIC_UPDATES = ['/api/news','/api/notifications'
];// 设置更新间隔(例如每小时更新一次)
const UPDATE_INTERVAL = 60 * 60 * 1000; // 1小时// 在SW激活后设置定期更新
self.addEventListener('activate', event => {// 其他激活代码...// 设置定期更新任务self.registration.periodicSync.register('update-cache', {minInterval: UPDATE_INTERVAL});
});// 处理定期同步事件
self.addEventListener('periodicsync', event => {if (event.tag === 'update-cache') {event.waitUntil(updateCache());}
});// 更新缓存的函数
function updateCache() {return Promise.all(PERIODIC_UPDATES.map(url => fetch(url).then(response => {if (!response.ok) return;const clonedResponse = response.clone();return caches.open(CACHE_NAME).then(cache => {return cache.put(url, clonedResponse);});}).catch(error => console.error('缓存更新失败:', url, error))));
}

缓存失效与更新策略

管理缓存的更新和失效是Service Worker实现中的关键挑战。以下是几种常用的缓存更新策略:

基于版本号的缓存更新

使用缓存版本号,在Service Worker更新时清除旧缓存:

const CACHE_VERSION = 'v2';
const CACHE_NAME = `app-cache-${CACHE_VERSION}`;self.addEventListener('activate', event => {event.waitUntil(caches.keys().then(cacheNames => {return Promise.all(cacheNames.map(cacheName => {// 删除不匹配当前版本的缓存if (cacheName.startsWith('app-cache-') && cacheName !== CACHE_NAME) {return caches.delete(cacheName);}}));}));
});
基于内容哈希的缓存更新

对于静态资源,使用内容哈希作为文件名的一部分,确保内容变化时URL也会变化:

// 在构建过程中生成带哈希的文件名
// main.8e2d4a2.js 而不是 main.js// 在Service Worker中预缓存这些资源
const CACHE_ASSETS = ['/','/index.html','/css/style.5f3e9b1.css','/js/main.8e2d4a2.js','/images/logo.3a7c4d8.png'
];
使用ETag进行缓存验证

对于API响应,可以使用ETag进行缓存验证,只在内容变化时更新缓存:

function updateWithEtag(request) {// 先检查缓存return caches.match(request).then(cachedResponse => {// 提取之前响应的ETagconst etag = cachedResponse ? cachedResponse.headers.get('ETag') : null;// 创建一个新的请求,包含If-None-Match头const newRequest = etag ? new Request(request, {headers: {'If-None-Match': etag}}) : request;// 发送带条件的请求return fetch(newRequest).then(networkResponse => {// 304表示内容未变化,使用缓存if (networkResponse.status === 304) {return cachedResponse;}// 内容已变化,更新缓存并返回新响应const clonedResponse = networkResponse.clone();caches.open(CACHE_NAME).then(cache => {cache.put(request, clonedResponse);});return networkResponse;}).catch(() => {// 网络请求失败,返回缓存(如果有)return cachedResponse;});});
}

处理Service Worker更新

Service Worker更新是一个需要谨慎处理的过程,以下是一种推荐的更新流程:

// 在主页面中监听Service Worker更新
if ('serviceWorker' in navigator) {// 注册Service Workernavigator.serviceWorker.register('/sw.js').then(registration => {// 检查更新registration.addEventListener('updatefound', () => {const newWorker = registration.installing;newWorker.addEventListener('statechange', () => {// 当新的Service Worker变为activated状态if (newWorker.state === 'activated') {// 通知用户页面有更新if (window.confirm('网站已更新,是否刷新页面以应用新版本?')) {window.location.reload();}}});});// 检查控制页面的Service Worker是否发生变化let refreshing = false;navigator.serviceWorker.addEventListener('controllerchange', () => {if (!refreshing) {refreshing = true;window.location.reload();}});});// 定期检查更新setInterval(() => {navigator.serviceWorker.getRegistration().then(registration => {if (registration) {registration.update();}});}, 60 * 60 * 1000); // 每小时检查一次
}

实战案例:构建离线可用的博客应用

以下是一个实际案例,展示如何为博客应用实现离线访问功能:

// sw.js - 博客应用的Service Worker// 缓存名称定义
const STATIC_CACHE = 'blog-static-v1';
const PAGES_CACHE = 'blog-pages-v1';
const IMAGES_CACHE = 'blog-images-v1';
const API_CACHE = 'blog-api-v1';// 需要缓存的静态资源
const STATIC_ASSETS = ['/','/index.html','/css/main.css','/js/app.js','/js/vendor.js','/offline.html','/images/logo.svg','/images/offline.svg'
];// 安装事件 - 预缓存静态资源
self.addEventListener('install', event => {event.waitUntil(caches.open(STATIC_CACHE).then(cache => cache.addAll(STATIC_ASSETS)).then(() => self.skipWaiting()));
});// 激活事件 - 清理旧缓存
self.addEventListener('activate', event => {const currentCaches = [STATIC_CACHE, PAGES_CACHE, IMAGES_CACHE, API_CACHE];event.waitUntil(caches.keys().then(cacheNames => {return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));}).then(cachesToDelete => {return Promise.all(cachesToDelete.map(cacheToDelete => caches.delete(cacheToDelete)));}).then(() => self.clients.claim()));
});// 拦截请求
self.addEventListener('fetch', event => {const request = event.request;const url = new URL(request.url);// 不处理非GET请求if (request.method !== 'GET') {return;}// 处理API请求if (url.pathname.startsWith('/api/')) {event.respondWith(handleApiRequest(request));return;}// 处理HTML页面请求if (request.headers.get('Accept').includes('text/html')) {event.respondWith(handleHtmlRequest(request));return;}// 处理图片请求if (url.pathname.match(/\.(jpg|jpeg|png|gif|svg)$/)) {event.respondWith(handleImageRequest(request));return;}// 处理其他静态资源event.respondWith(caches.match(request).then(cachedResponse => cachedResponse || fetchAndCache(request, STATIC_CACHE)));
});// 处理API请求
function handleApiRequest(request) {return fetchWithTimeout(request, 3000).then(response => {// 复制响应以便缓存const clonedResponse = response.clone();// 只缓存成功的响应if (response.ok) {caches.open(API_CACHE).then(cache => cache.put(request, clonedResponse));}return response;}).catch(() => {// 网络请求失败,尝试从缓存获取return caches.match(request).then(cachedResponse => {if (cachedResponse) {// 给缓存响应添加标记,表明来自缓存const responseOptions = {headers: new Headers(cachedResponse.headers),status: cachedResponse.status,statusText: cachedResponse.statusText};responseOptions.headers.set('X-Data-Source', 'cache');return cachedResponse.blob().then(body => new Response(body, responseOptions));}// 如果没有缓存,返回离线API响应return new Response(JSON.stringify({error: 'offline',message: '您当前处于离线状态,无法获取最新数据'}),{status: 503,headers: { 'Content-Type': 'application/json' }});});});
}// 处理HTML请求
function handleHtmlRequest(request) {return fetchWithTimeout(request, 3000).then(response => {// 复制响应并缓存const clonedResponse = response.clone();caches.open(PAGES_CACHE).then(cache => cache.put(request, clonedResponse));return response;}).catch(() => {// 网络请求失败,尝试从缓存获取return caches.match(request).then(cachedResponse => cachedResponse || caches.match('/offline.html'));});
}// 处理图片请求
function handleImageRequest(request) {// 使用缓存优先策略return caches.match(request).then(cachedResponse => {if (cachedResponse) {// 同时在后台更新缓存fetch(request).then(networkResponse => {if (networkResponse.ok) {caches.open(IMAGES_CACHE).then(cache => cache.put(request, networkResponse));}}).catch(() => {/* 静默失败 */});return cachedResponse;}// 缓存中没有,发起网络请求return fetch(request).then(networkResponse => {// 缓存响应副本const clonedResponse = networkResponse.clone();caches.open(IMAGES_CACHE).then(cache => cache.put(request, clonedResponse));return networkResponse;}).catch(() => {// 返回占位图片return caches.match('/images/offline.svg');});});
}// 通用的获取并缓存函数
function fetchAndCache(request, cacheName) {return fetch(request).then(response => {if (response.ok) {const clonedResponse = response.clone();caches.open(cacheName).then(cache => cache.put(request, clonedResponse));}return response;}).catch(error => {console.error('获取资源失败:', error);throw error;});
}// 添加超时的fetch函数
function fetchWithTimeout(request, timeout) {return new Promise((resolve, reject) => {// 设置超时const timeoutId = setTimeout(() => {reject(new Error('请求超时'));}, timeout);fetch(request).then(response => {clearTimeout(timeoutId);resolve(response);},err => {clearTimeout(timeoutId);reject(err);});});
}

通过上述Service Worker实现,博客应用能够实现:

  1. 离线浏览已访问过的页面
  2. 即使在线,也能快速加载缓存资源
  3. 针对不同资源类型应用不同的缓存策略
  4. 提供优雅的离线体验和失败处理

Service Worker强大的缓存控制能力和离线支持,使它成为现代Web应用性能优化和用户体验提升的关键技术。在下一节中,我们将探讨另一种客户端存储技术——IndexedDB,它为Web应用提供了更强大的本地数据库能力。

IndexedDB高性能客户端存储应用

IndexedDB基础概念

IndexedDB是一种低级API,用于在客户端存储大量结构化数据。与localStorage和sessionStorage不同,IndexedDB提供了完整的事务性数据库系统,支持索引、游标和事务,能够高效处理大量数据。

IndexedDB的主要特点
  1. 大容量存储:可以存储远超localStorage的数据量(通常为数百MB甚至GB)
  2. 结构化数据:支持JavaScript对象直接存储,无需序列化
  3. 事务支持:提供类似传统数据库的事务隔离
  4. 异步API:所有操作都是异步的,不会阻塞主线程
  5. 同源策略:遵循浏览器同源策略,保证安全性
  6. 索引查询:可以为数据创建索引,支持高效查询
IndexedDB的基本工作流程
  1. 打开数据库连接
  2. 在连接回调中创建对象仓库(object store)
  3. 启动一个事务并请求执行操作
  4. 通过事件监听操作结果
  5. 使用查询方法或游标获取数据

创建和管理IndexedDB数据库

打开数据库
function openDatabase() {return new Promise((resolve, reject) => {// 打开数据库(如果不存在则创建)const request = indexedDB.open('MyAppDB', 1);// 处理数据库升级事件request.onupgradeneeded = event => {const db = event.target.result;// 创建对象仓库if (!db.objectStoreNames.contains('users')) {const usersStore = db.createObjectStore('users', { keyPath: 'id' });// 创建索引usersStore.createIndex('email', 'email', { unique: true });usersStore.createIndex('name', 'name', { unique: false });}if (!db.objectStoreNames.contains('articles')) {const articlesStore = db.createObjectStore('articles', { keyPath: 'id' });articlesStore.createIndex('author', 'authorId', { unique: false });articlesStore.createIndex('date', 'publishDate', { unique: false });}};// 成功回调request.onsuccess = event => {const db = event.target.result;resolve(db);};// 错误回调request.onerror = event => {console.error('打开数据库失败:', event.target.error);reject(event.target.error);};});
}
添加数据
function addItem(db, storeName, item) {return new Promise((resolve, reject) => {// 创建读写事务const transaction = db.transaction([storeName], 'readwrite');const store = transaction.objectStore(storeName);// 添加或更新记录const request = store.put(item);// 处理结果request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);// 事务完成处理transaction.oncomplete = () => console.log('事务完成');transaction.onerror = event => console.error('事务错误:', event.target.error);});
}
获取数据
function getItem(db, storeName, key) {return new Promise((resolve, reject) => {const transaction = db.transaction([storeName], 'readonly');const store = transaction.objectStore(storeName);const request = store.get(key);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});
}
查询数据
function queryByIndex(db, storeName, indexName, value) {return new Promise((resolve, reject) => {const transaction = db.transaction([storeName], 'readonly');const store = transaction.objectStore(storeName);const index = store.index(indexName);const request = index.getAll(value);request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});
}
使用游标遍历数据
function iterateWithCursor(db, storeName, callback) {return new Promise((resolve, reject) => {const transaction = db.transaction([storeName], 'readonly');const store = transaction.objectStore(storeName);const results = [
http://www.xdnf.cn/news/261703.html

相关文章:

  • JavaScript:pc端网页特效--元素偏移量offset系列
  • flask 获取各种请求数据:GET form-data x-www-form-urlencoded JSON headers 上传文件
  • 每天学一个 Linux 命令(35):dos2unix
  • SMT贴片检验标准核心要点与实施规范
  • MySQL进阶(三)
  • 欺骗单页应用(SPA)渲染隐藏路由 -- trouble at the spa b01lersCTF
  • Ubuntu 24.04 完整Docker安装指南:从零配置到实战命令大全
  • NuttX 与 PX4 系统开发全流程详解
  • 《AI大模型应知应会100篇》第46篇:大模型推理优化技术:量化、剪枝与蒸馏
  • 基于 SAFM 超分辨率上采样模块的 YOLOv12 改进方法—模糊场景目标检测精度提升研究
  • 一、大模型原理:第一步分词Tokenization
  • MySQL数据操作全攻略:DML增删改与DQL高级查询实战指南
  • hadoop存储数据文件原理
  • 微服务设计约束
  • 介绍 PHP-FPM 和 Python WSGI
  • 【Bootstrap V4系列】学习入门教程之 组件-按钮(Buttons)
  • 宝塔docker拉取仓库失败
  • Nginx发布Vue(ElementPlus),与.NETCore对接(腾讯云)
  • list的迭代器详讲
  • Linux之基础开发工具(yum,vim,gcc,g++)
  • 基于 Dify + vLLM插件 + Qwen3 构建问答机器人Docker版
  • 【计算机视觉】三维重建: OpenMVS:工业级多视图立体视觉重建框架
  • ActiveMQ 集群搭建与高可用方案设计(二)
  • 生成对抗网络(GAN, Generative Adversarial Network)​
  • More Effective C++学习笔记
  • ShaderToy学习笔记 08.阴影
  • 力扣:24两两交换链表的节点
  • 搭建一个 gRPC 服务端和客户端
  • 【Java学习】关于springBoot的自动配置和起步依赖
  • CSS 预处理器 Sass