主题切换
附录 A:性能优化指南
Electron 应用常被诟病"内存占用高"、"启动慢"。其实,通过合理的优化策略,Electron 应用完全可以达到接近原生的体验。本附录将从主进程、渲染进程、内存管理和包体积四个维度,提供可落地的优化方案。
A.1 启动速度优化
延迟加载非关键模块
主进程中不要一次性导入所有模块,按需加载:
javascript
// 不推荐:启动时加载所有模块
const { autoUpdater } = require('electron-updater')
const { setupCrashReporter } = require('./crash')
// 推荐:按需延迟加载
let autoUpdater
function checkUpdate() {
if (!autoUpdater) {
autoUpdater = require('electron-updater').autoUpdater
}
autoUpdater.checkForUpdates()
}窗口预加载策略
使用 BrowserWindow 的 show: false 先初始化窗口,内容加载完成后再显示:
javascript
const win = new BrowserWindow({
show: false, // 先不显示
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
win.loadURL('http://localhost:5173')
// 内容就绪后再显示,避免白屏
win.once('ready-to-show', () => {
win.show()
win.focus()
})使用 V8 代码缓存
javascript
app.commandLine.appendSwitch('js-flags', '--max-old-space-size=512')
app.commandLine.appendSwitch('enable-features', 'V8CodeCache')A.2 渲染进程性能
虚拟滚动处理大数据列表
当列表超过 1000 项时,使用虚拟滚动:
vue
<template>
<RecycleScroller
class="scroller"
:items="items"
:item-size="48"
key-field="id"
>
<template #default="{ item }">
<div class="item">{{ item.name }}</div>
</template>
</RecycleScroller>
</template>
<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
</script>图片懒加载
vue
<template>
<img v-lazy="imageSrc" :alt="title" />
</template>
<script setup>
import { useLazyload } from 'vue3-lazyload'
</script>减少不必要的重渲染
使用 shallowRef 和 markRaw 避免深层响应式带来的性能损耗:
javascript
import { shallowRef, markRaw } from 'vue'
// 大数据对象不需要深层响应式
const chartData = shallowRef(null)
// 第三方类实例标记为 raw
const editor = markRaw(new Editor())A.3 内存管理
及时释放不再使用的窗口
javascript
function createModalWindow() {
const modal = new BrowserWindow({
parent: mainWindow,
modal: true,
width: 600,
height: 400
})
modal.loadFile('modal.html')
// 窗口关闭时移除引用,允许 GC 回收
modal.on('closed', () => {
// modal 自动被销毁,无需手动 null 赋值
})
}监控内存使用
javascript
// 定期检查内存占用
setInterval(() => {
const usage = process.memoryUsage()
console.log(`Heap Used: ${(usage.heapUsed / 1024 / 1024).toFixed(2)} MB`)
console.log(`RSS: ${(usage.rss / 1024 / 1024).toFixed(2)} MB`)
}, 30000)使用 Chrome DevTools 分析内存
- 启动应用并打开 DevTools(
Ctrl+Shift+I) - 切换到 Memory 面板
- 选择 Heap snapshot,点击拍摄快照
- 执行一些操作后再次拍摄快照
- 对比两个快照,查找 retained size 异常大的对象
内存泄漏常见原因
- 事件监听未移除:
ipcRenderer.on没有对应的removeListener - 闭包引用:
setInterval回调中持有大对象引用 - DOM 引用:Vue 组件销毁后仍有外部引用指向 DOM 节点
- 窗口未关闭:创建的
BrowserWindow没有被关闭和销毁
A.4 包体积优化
Tree Shaking 未使用代码
确保 package.json 中设置了 sideEffects:
json
{
"name": "my-electron-app",
"sideEffects": false
}精简原生模块
只打包必要的平台二进制文件:
yaml
# electron-builder.yml
extraResources:
- from: "node_modules/better-sqlite3/build/Release/"
to: "./"
filter:
- "better_sqlite3.node"
# 排除开发依赖和测试文件
files:
- "!**/*.test.js"
- "!**/*.spec.js"
- "!**/docs/**"asar 配置优化
yaml
# electron-builder.yml
asar: true
asarUnpack:
- "node_modules/better-sqlite3/**"
- "node_modules/sharp/**"
- "**/*.node" # 解包所有原生模块,避免性能损耗A.5 主进程性能
避免阻塞主进程
javascript
// 不推荐:同步读取大文件
const data = fs.readFileSync('large-file.csv') // 阻塞!
// 推荐:异步读取
const data = await fs.promises.readFile('large-file.csv')
// 或流式处理
const stream = fs.createReadStream('large-file.csv')
stream.on('data', (chunk) => {
processChunk(chunk)
})使用 setImmediate 分解大任务
javascript
async function processLargeArray(items) {
const batchSize = 100
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize)
await processBatch(batch)
// 让出主线程,处理其他事件
await new Promise(resolve => setImmediate(resolve))
}
}性能优化检查清单
- [ ] 使用
show: false+ready-to-show避免白屏 - [ ] 按需加载模块,避免启动时全量导入
- [ ] 大数据列表使用虚拟滚动
- [ ] 及时移除 IPC 事件监听和定时器
- [ ] 定期使用 DevTools Memory 面板检查泄漏
- [ ] 打包时排除测试文件和文档
- [ ] 原生模块使用
asarUnpack解包
A.6 代码分割与懒加载
利用 Vite 的代码分割能力,将渲染进程拆分为更小的 chunk,显著减少首屏加载时间:
typescript
// Vue Router 路由懒加载
const routes = [
{
path: '/editor',
// 仅在访问时加载编辑器模块
component: () => import('../views/FlowEditor.vue')
},
{
path: '/settings',
component: () => import('../views/SettingsView.vue')
}
]
// 异步组件
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() =>
import('../components/HeavyChart.vue')
)typescript
// vite.config.ts — 手动分包策略
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'element-plus': ['element-plus'],
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'editor': ['@nut-tree/nut-js', 'tesseract.js']
}
}
}
}
})A.7 Web Workers 与 Worker Threads
将计算密集任务(如图片处理、OCR、大数据分析)移到 Worker 中,避免阻塞主线程:
javascript
// 主进程:创建 Worker Thread
const { Worker } = require('worker_threads')
function processImageInWorker(imagePath) {
return new Promise((resolve, reject) => {
const worker = new Worker('./workers/image-processor.js', {
workerData: { imagePath }
})
worker.on('message', resolve)
worker.on('error', reject)
worker.on('exit', (code) => {
if (code !== 0) reject(new Error(`Worker 异常退出: ${code}`))
})
})
}javascript
// 渲染进程:使用 Web Worker
const ocrWorker = new Worker(
new URL('../workers/ocr.worker.ts', import.meta.url),
{ type: 'module' }
)
ocrWorker.postMessage({ image: imageData })
ocrWorker.onmessage = (event) => {
console.log('OCR 结果:', event.data.text)
}A.8 渲染帧率监控(FPS)
使用 requestAnimationFrame 检测渲染进程的帧率,及时发现性能瓶颈:
typescript
// 渲染进程 FPS 监控
let lastTime = performance.now()
let frames = 0
let fps = 60
function measureFPS() {
frames++
const now = performance.now()
if (now >= lastTime + 1000) {
fps = Math.round((frames * 1000) / (now - lastTime))
frames = 0
lastTime = now
// 低于 30fps 时记录警告
if (fps < 30) {
console.warn(`FPS 过低: ${fps}`)
}
}
requestAnimationFrame(measureFPS)
}
requestAnimationFrame(measureFPS)A.9 快速见效的优化(Quick Wins)
| 优化手段 | 投入 | 效果 | 适用于 |
|---|---|---|---|
show: false + ready-to-show | 极低 | 消除白屏闪烁 | 所有应用 |
| 路由懒加载 | 低 | 减少 30-50% 首屏 JS | 多页面应用 |
| 虚拟滚动 | 中 | 10000+ 列表项流畅滚动 | 数据密集型应用 |
| 按需加载原生模块 | 低 | 减少 200-500ms 启动时间 | 使用原生模块的应用 |
shallowRef 替代 ref | 极低 | 大对象响应式开销降 80% | Vue 组件 |
| Worker 线程 | 中 | UI 帧率从 15fps 恢复到 60fps | 计算密集任务 |