Electron: 缓存机制有哪些?能控制的又有哪些

Electron 缓存机制深度解析

在 Electron (包括 VS Code 等应用) 的架构中,缓存机制是一个涉及 Chromium 渲染引擎V8 JavaScript 引擎 以及 Node.js 运行时 的多层体系。本文把 Electron 缓存拆成四个层面------V8 代码缓存、GPU 着色器缓存、HTTP 网络缓存、Service Worker 缓存,每一层都从生成、生效、失效、开发者把控四个角度展开。


结论先行

常见有四层缓存由内到外,分别作用于 JS 执行、GPU 渲染、网络资源和离线分发,各自独立又相互补充。下表把四类缓存的存放位置、命中时机、失效触发和开发者手段汇总到一起,方便对照:

缓存类型 存放位置 命中时机 失效触发 开发者手段
V8 代码缓存 Code Cache / CachedData 同一 JS 文件短时间内二次加载,且路径、大小、修改时间、V8 版本一致 代码修改、Electron / V8 升级 v8-compile-cacheenableCompileCache()、bytenode 预编译
GPU 着色器缓存 GPUCache 相同 CSS 滤镜 / WebGL 效果再次渲染 显卡驱动或硬件更换、DirectX / OpenGL 版本变化 禁用 GPU 磁盘缓存、重定向缓存路径
HTTP 网络缓存 Cache 资源在有效期内再次请求 缓存过期、ETag / Last-Modified 协商失败 拦截请求修改缓存策略、clearCache() / clearStorageData()
Service Worker 缓存 CacheStorage 命中开发者配置的缓存策略(缓存优先 / 网络优先 / 渐进式更新) SW 脚本更新、Cache 版本迭代、磁盘配额耗尽 Workbox、skipWaiting()caches.delete()

四层缓存中,V8 与 GPU 缓存由引擎自动管理、开发者干预空间有限;HTTP 缓存遵循标准协议、开发者可拦截改写;Service Worker 缓存则完全由代码掌控,是最灵活也最需要谨慎维护的一层。理解各自的命中与失效边界,才能在启动速度、资源新鲜度和维护成本之间做出合适的取舍。

一、 V8 代码缓存机制 (V8 Code Cache / Bytecode Cache)

由于 JavaScript 是解释型/即时编译型语言,应用启动时,V8 引擎需要解析并编译大量的 JS 文件。为了加速这一过程,V8 引入了代码缓存。

如何生成?

  • 双重加载规则 :在运行期间,当 Chromium 或 Node.js 第一次加载并编译一个 JS 文件时,它仅正常执行。如果该文件在很短的时间内被加载了第二次 (且大小通常大于几 KB),V8 就会认为这是一个"热点文件",并把编译好的 字节码 (Bytecode) 提取出来,序列化存入 Code CacheCachedData 目录。
  • 冷启动与热启动:用户首次安装打开(冷启动)时生成缓存,后续打开(热启动)时直接读取。

如何生效与失效?

  • 生效 (Hit):当应用试图再次加载该 JS 文件时,V8 会对比:

    1. 文件的完整路径与 URL。
    2. 文件的大小和修改时间(或者哈希值)。
    3. V8 引擎的版本号 。 如果完全一致,V8 跳过"下载 -> 解析 (Parsing) -> 编译 (Compiling)"的过程,直接从磁盘加载字节码并执行,能节省高达 30% ~ 50% 的 JS 加载时间。
  • 失效 (Miss/Invalidate)

    • 代码更新:只要开发者修改了 JS 文件的一行代码,其哈希或修改时间改变,缓存立即失效并重新生成。
    • 应用升级V8 的字节码不具备跨版本兼容性。一旦 Electron 升级(即底层的 V8 引擎版本变了),历史的所有字节码缓存将瞬间全部失效,引擎会在第一次启动时默默将其清除并重新生成。

开发者能做什么?

  • VS Code 的做法 (v8-compile-cache) :VS Code 的开发者为了极致的启动速度,开发了名为 v8-compile-cache 的 Node 库。它可以在应用首次运行时,利用 Node.js 的 vm 模块在内存中编译代码,并将字节码手动序列化保存到硬盘(也就是您看到的 CachedData)。在主进程引入它,能显著提升启动速度:
JavaScript 复制代码
require('v8-compile-cache'); // 必须放在应用入口的最顶端
  • Node.js 22+ 原生支持:现代 Node.js 已经内置了原生编译缓存,可以通过代码启用:
JavaScript 复制代码
import { enableCompileCache } from 'node:module';
enableCompileCache();
  • 字节码预打包 (bytenode) :如果不想等用户首次运行才生成,开发者可以使用 bytenode 工具,在打包构建阶段就直接把 JS 文件预编译为 .jsc 字节码文件发布。这样既能保护源代码不被反编译 ,又能实现首开即热启动

二、 GPU 着色器缓存 (GPU Shader Cache)

说完 V8 层的字节码缓存,再来看 GPU 这一层。GPU 渲染网页时,需要将 CSS 滤镜、WebGL 效果等编译为 GPU 显卡可以直接执行的着色器二进制机器码。

如何生成?

  • 当应用内的 CSS 动画、Canvas、WebGL 或 3D 渲染组件首次工作时,Chromium 会调用系统显卡驱动编译着色器语言 (GLSL/HLSL)。
  • 为了避免重复编译导致掉帧或卡顿,Chromium 会把编译好的二进制 Shader 存储在 GPUCache 目录中。

如何生效与失效?

  • 生效:渲染相同元素时,直接调用显卡读取二进制 Shader 缓存,实现秒开且无掉帧。

  • 失效

    • 用户更新了显卡驱动
    • 用户更换了显卡硬件
    • 系统的 DirectX / OpenGL 版本发生改变。 一旦发生上述变更,历史着色器二进制码可能在新的驱动上无法运行,引擎会自动失效旧缓存,并在首次启动时重新编译。

开发者能做什么?

  • 如果用户的低端显卡在读取 GPU 缓存时经常出现花屏、黑屏等 Bug,开发者可以在 Electron 启动时禁用 GPU 缓存
JavaScript 复制代码
// 在 main.js 中,app ready 之前
app.commandLine.appendSwitch('disable-gpu-shader-disk-cache');
  • 开发者也可以重定向缓存路径,防止其污染用户的系统目录:
JavaScript 复制代码
app.commandLine.appendSwitch('disk-cache-dir', '/path/to/custom/cache');

三、 HTTP 网络与资源缓存 (Network Cache)

说完 GPU 层的着色器缓存,再来看最传统的 HTTP 网络缓存------这是网页资源缓存(存放在 Cache 目录中)。

如何生成与生效?

  • Electron 内的网页(无论是本地的 file:// 还是远程的 http://)在请求图片、CSS、字体、JS 时,遵循标准的 HTTP 缓存协议 (如 Cache-ControlETagLast-Modified)。

开发者能做什么?

  • 拦截与修改缓存行为 :开发者可以通过 Electron 的 session 模块,拦截网络请求并强制自定义缓存策略:
JavaScript 复制代码
const { session } = require('electron');
// 禁用整个 session 的缓存
session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
  details.requestHeaders['Cache-Control'] = 'no-cache';
  callback({ cancel: false, requestHeaders: details.requestHeaders });
});
  • 手动清理缓存:Electron 提供了 API,允许开发者在检测到版本更新、或者用户主动点击"清理缓存"时,从代码端一键清除:
JavaScript 复制代码
// 清理 HTTP 缓存
await session.defaultSession.clearCache();
// 清理 Storage 缓存(包括 LocalStorage, Service Workers 等)
await session.defaultSession.clearStorageData({
  storages: ['appcache', 'serviceworkers', 'cachestorage']
});
  • Service Worker 离线缓存:对于混合应用,开发者常用 Service Worker 结合 CacheStorage 来进行精确的离线缓存控制,详细原理见第四部分。

四、 Service Worker 与 CacheStorage 缓存机制

前三层缓存由 Chromium 按引擎规则自动管理,而 Service Worker 则把缓存的控制权完全交到了代码手里。Service Worker (简称 SW) 是运行在浏览器后台的独立线程,充当客户端可编程的网络代理。它与 CacheStorage 配合,允许开发者通过 JavaScript 代码完全控制网络请求的拦截、缓存与分发。

如何生成与安装?

  • 第一阶段:注册 (Registration) :主页面通过调用 navigator.serviceWorker.register('/sw.js') 来注册 Service Worker 脚本。
  • 第二阶段:安装 (Installation) :Chromium 下载并解析 sw.js 后触发 install 事件。开发者通常在此事件中预先打开一个 CacheStorage 实例,将应用所需的核心静态资源(如 HTML、CSS、JS、字体和本地图片)全部拉取并缓存起来:
JavaScript 复制代码
self.addEventListener('install', event => {
  event.waitUntil(
    caches.open('app-shell-v1').then(cache => {
      return cache.addAll([
        '/',
        '/index.html',
        '/styles.css',
        '/bundle.js'
      ]);
    })
  );
});
  • 第三阶段:激活 (Activation) :安装完成后,新 Service Worker 会进入等待激活状态。激活时触发 activate 事件,常在此处执行旧版 Cache 数据的清理工作。

什么时候生效?

一旦 Service Worker 成功激活,并且后续的页面请求在其作用域内,它便可以通过监听 fetch 事件拦截所有的网络请求。此时,缓存是否生效完全取决于开发者配置的缓存策略

  • 缓存优先 (Cache-First)

    • 逻辑 :发起请求时,首先检查 CacheStorage 是否有匹配的缓存项。如果有,立即返回缓存(可实现秒开和离线工作);如果没有,再去发起网络请求,并将响应存入缓存。
    • 生效时机:只要资源曾经被加载过并成功写入 CacheStorage,第二次加载时即永久生效,直到被主动清除。
  • 网络优先 (Network-First)

    • 逻辑:首先尝试从网络拉取最新资源。若网络顺畅且拉取成功,则更新缓存并返回;若网络连接失败或超时(如离线状态),则降级使用 CacheStorage 中的旧缓存。
    • 生效时机:仅在断网或网络极差导致请求失败时生效。
  • 渐进式更新 (Stale-While-Revalidate)

    • 逻辑 :页面发起请求时,Service Worker 立即返回 Cache 中的旧数据(极速响应),同时在后台发起网络请求拉取最新的资源并默默更新 CacheStorage。
    • 生效时机:只要本地有缓存就会立刻生效,但用户会在下一次打开时才看到后台默默下载好的最新页面内容。

什么时候失效?

由于 Service Worker 完全由代码掌控,它的失效和更新逻辑分为 Service Worker 脚本更新 以及 Cache 缓存项更新 两部分:

  • Service Worker 脚本的失效/更新

    • Chromium 默认会拦截对 sw.js 的请求,并执行 逐字节对比(Byte-for-byte check)。
    • 如果检测到服务器/本地的 sw.js 发生了哪怕 1 字节的变化,或者距离上一次检查已过去 24 小时,Chromium 就会下载新脚本并尝试安装。
    • 新的 SW 安装成功后会进入 waiting 状态,只有在所有打开该应用的窗口/标签页被关闭重启(或者代码中调用了 self.skipWaiting())后,新 SW 才会激活,旧 SW 彻底失效。
  • CacheStorage 缓存项的失效/更新

    • 开发者主动版本迭代 :最常见的失效方式是开发者修改了 Cache 名称(如从 app-shell-v1 升级为 app-shell-v2)。在新 SW 激活时,代码会自动删除旧版本的 Cache:
    JavaScript 复制代码
    self.addEventListener('activate', event => {
      event.waitUntil(
        caches.keys().then(keys => {
          return Promise.all(
            keys.filter(key => key !== 'app-shell-v2').map(key => caches.delete(key))
          );
        })
      );
    });
    • API 主动清除 :调用 caches.delete(cacheName) 或在应用内使用 cache.delete(request) 手动让特定资源的缓存失效。
    • 磁盘空间配额耗尽:Chromium 会根据磁盘容量自动设置缓存配额。虽然概率极低,但在系统磁盘极端缺乏空间时,Chromium 的存储管理器(Storage Manager)会自动清空整个 CacheStorage 以释放磁盘。
waiting 状态如何主动通知渲染进程?

当新的 SW 脚本下载并安装完毕、进入 waiting 状态时,Chromium 不会自动向渲染进程弹出界面通知 ,而是通过 Service Worker API 触发一系列事件。开发者需要在**渲染进程(Web 页面)**中主动监听这些事件,捕获到 waiting 状态后,弹出"检测到新版本,是否立即更新?"的提示。整个协同过程合并为一个精简示例:

JavaScript 复制代码
// 渲染进程(Web 页面):监听新版本,提示用户
navigator.serviceWorker.register('/sw.js').then(reg => {
  const notify = worker => {          // 进入 waiting 后询问用户
    if (worker && worker.state === 'installed' &&
        confirm('应用有新版本,是否立即更新?')) {
      worker.postMessage({ action: 'skipWaiting' }); // 通知 SW 跳过等待
    }
  };
  notify(reg.waiting);                // 情况 A:已有等待中的新 SW
  reg.addEventListener('updatefound', () =>  // 情况 B:运行中发现新脚本
    notify(reg.installing));
});

// sw.js:收到指令后立即激活,接管页面
self.addEventListener('message', e => {
  if (e.data && e.data.action === 'skipWaiting') self.skipWaiting();
});

// 渲染进程:新 SW 激活后刷新页面,呈现新版内容
navigator.serviceWorker.addEventListener('controllerchange', () => {
  location.reload();
});

开发者能做什么?

  • 使用高级库管理 :手写 Service Worker 逻辑复杂且极易因代码逻辑漏洞导致页面文件"死锁"(如用户永远无法更新到新版页面)。强烈建议使用 Google 的官方库 Workbox 进行路由配置和策略缓存。
  • 防止 sw.js 自身被 HTTP 强缓存 :一定要确保您的 Web 服务器对 sw.js 文件本身设置了 Cache-Control: no-cache 响应头。否则,如果 sw.js 被浏览器强缓存,客户端将永远检测不到 SW 代码的变更,应用将永远无法更新。

五、其他容易被忽略的缓存层

除了上面四类主要缓存,Chromium 内核(也就是 Electron 的渲染引擎)里还散布着一些零散但同样影响体验的缓存层,按"网络栈 → 策略 → 应用层"的顺序梳理如下:

  • 内存缓存 (Memory Cache) :网络资源的第一级"快取"。最近请求过的图片、JS、CSS 等会先放在渲染进程内存里,命中只需微秒级,比磁盘缓存更快;但它随标签页/进程关闭而消失。完整的资源查找链是:Service Worker → 内存缓存 → 磁盘缓存 → 网络
  • DNS 缓存 :Chromium 自带浏览器级 DNS 缓存(chrome://net-internals/#dns 可查看),把"域名 → IP"的解析结果缓存下来,省去每次连接约 20~120ms 的解析耗时。失效由 TTL 控制,清空通常需要重启应用。
  • TLS 会话缓存与 Socket 连接池:HTTPS 握手结果(Session Ticket)会被缓存复用,配合 keep-alive 连接池,避免每次请求都重新握手、重新建连。会话过期或进程重启后失效。
  • CORS 预检缓存 :跨域复杂请求的 OPTIONS 预检(Preflight)结果会按 Access-Control-Max-Age 缓存,期限内不再重复发送预检请求;服务器可通过调整该响应头控制缓存时长。
  • HSTS 缓存 :服务器通过 Strict-Transport-Security 响应头声明"本域只允许 HTTPS",Chromium 会缓存该策略以防 HTTPS 降级攻击;max-age 过期或用户清除浏览数据后失效。
  • 图标缓存 (Favicon Cache) :站点图标与文件图标(app.getFileIcon())的缩略结果由 Chromium 以 SQLite 数据库(Favicons)形式缓存,重复渲染时秒出。
  • 媒体缓存 (Media Cache):Chromium 的磁盘缓存中有一块专门服务音视频的媒体缓存,按需缓存媒体数据块,避免播放时反复请求网络。
  • 附:容易被误认为缓存的存储族 :LocalStorage、SessionStorage、IndexedDB、WebSQL、FileSystem、Cookies 等属于持久化存储而非缓存,但 Electron 的 session.clearStorageData() 会把它们与缓存统一管理(storages 可选类型包括 cookies、filesystem、indexdb、localstorage、shadercache、websql、serviceworkers、cachestorage)。已废弃的 Application Cache(AppCache)也曾由磁盘缓存提供后端支持,如今不建议使用。

与四层主缓存不同,这些零散缓存大多由 Chromium 自动管理、没有独立 API 可精准干预,开发者能做的通常是整体清除(clearCache() / clearStorageData()),或通过响应头控制策略类缓存(CORS、HSTS)的时效。

相关推荐
计算机魔术师1 小时前
一边喊安全一边烧钱竞赛:AI「减速」倡议背后的两副面孔
前端
右耳朵猫AI1 小时前
Web前端周刊2026W37 | Shopify 转原生、React 编译 Rust 化、Vitest 5.0、Rslib 1.0
前端·javascript·react.js·typescript·node.js
涛涛ing1 小时前
2026年,这5个JS新趋势正在悄悄改写前端
前端
moonsims1 小时前
AiBrainBox-UGV 的目标跟踪从“传统视觉跟踪”升级为“语义目标跟踪”
前端·人工智能·量子计算
驭渊的小故事1 小时前
Spring Boot 注解详解01
java·前端·spring boot
pe7er2 小时前
引子
前端·后端·架构
恋猫de小郭2 小时前
CPF-Flutter 社区提出折叠场景分栏(平行视界) 方案
android·前端·flutter
aixingpan2 小时前
aixingpan.cn API开发文档:api_docs_trichart_natal_marx_transit2接口指南
前端·php
csj502 小时前
前端基础之《React(13)—表单绑定、列表渲染》
前端·react.js