本节摘要:多窗口应用的内存随窗口数线性增长,控制手段是按窗口价值选择"销毁、隐藏、隐藏加节流"三级策略;后台窗口要让出 CPU 与网络;最终靠性能预算机制把优化成果制度化。本节给出窗口生命周期策略表、后台节流的实现手段与一个多窗口编辑器的策略设计案例。
2.1 节的进程模型决定了:每开一个窗口就多一个渲染进程,即便加载同一个页面也各自持有独立的 DOM、JS 堆与合成资源。空窗口的基线成本就有几十兆,重度页面轻松上百兆——多窗口应用的资源策略不是锦上添花,而是架构决策。核心是一张三级策略表:
| 策略 | 内存占用 | 恢复成本 | 适用窗口 |
|---|---|---|---|
| 销毁(destroy 后重建) | 归零 | 高:重新加载与初始化 | 一次性窗口:设置、关于、导入向导 |
| 隐藏(hide) | 全额保留 | 极低:show 即回 | 频繁切换且状态昂贵的窗口 |
| 隐藏加节流 | 保留但降耗 | 低 | 常驻后台:托盘小窗、下载面板 |
选型的判据是恢复成本与切换频率的乘积:用户一天开关二十次的编辑器子窗口,销毁重建的体验代价不可接受;一年打开一次的关于对话框,保留纯属浪费。默认答案可以概括为:临时窗口用完即销毁,常用窗口隐藏保状态,常驻窗口隐藏加节流。
隐藏着的窗口如果不加约束,定时器照跳、轮询照发、动画照算——用户看不见,资源照烧。三类手段:
定时器感知。页面 visibilitychange 事件在窗口隐藏时触发,借此停掉非必要的定时任务:
// 页面:可见性驱动的任务开关 let pollTimer = null; function startPolling() { stopPolling(); pollTimer = setInterval(pollServer, 10000); } function stopPolling() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } } document.addEventListener('visibilitychange', () => { document.hidden ? stopPolling() : startPolling(); // 后台停轮询,回前台恢复 });
主进程侧的背景模式。Electron 提供了后台节流的配置位:隐藏窗口的定时器与动画可被框架自动降频(powerSaveBlocker 的反面)。对托盘常驻类应用,这个开关往往是"后台 CPU 从百分之几降到接近零"的免费午餐。
网络自律。后台窗口的数据刷新降级为低频,甚至改为"回前台时一次补齐"。聊天应用的做法有代表性:后台窗口只维持长连接收关键推送,历史消息与媒体资源等回前台再拉。
优化最大的敌人是回退:这个季度辛苦压下去的内存,下个季度两个新功能就吃回去了。性能预算(performance budget)是防回退机制——给关键指标定红线,接进 CI 持续盯防:
# 性能预算示意(接入构建流水线,超标即红灯) 冷启动首帧就绪 ≤ 1600 ms (测量方式:正式构建 + 记录事件时间戳) 空窗口内存 ≤ 220 MB (三平台取最大值) 首屏 bundle ≤ 1 MB (构建产物报告直接读数) 后台 CPU ≤ 1% (隐藏窗口静置五分钟采样)
预算数字的来历要能讲清楚:以当前优化后的水平为基线,留少量余量,而不是拍脑袋定一个"看起来美"的数。每次版本发布对比预算表,超标的功能在评审会上要么优化、要么 consciously 地调整预算并留下记录——预算是谈判桌,不是刑场,但谈判要留案底——每次预算调整的评审记录里写清楚"谁提出的、为什么、预期影响什么指标",半年后回看这些案底,它就是应用资源演化的编年史,比任何一次性的性能报告都有说服力。,但谈判要留案底。

背景:一个文档编辑器,用户习惯同时开七八个文档窗口,还常驻一个大纲小窗与一个预览窗。优化前,十个窗口的常驻内存逼近两吉字节,老机器用户苦不堪言。
操作:策略重构按三级表落地——文档窗口改为"隐藏保状态,超过上限的销毁重建":维护一个窗口池,最近使用的六个保留隐藏,更久未用的销毁并在切换时重建(配合 5.2 节的启动优化,重建已压到半秒内,体验可接受);大纲与预览窗常驻但接后台节流,隐藏时停轮询停动画;预览窗的媒体资源改为回前台按需加载。同时上线四项性能预算接入 CI。
结果:典型十窗口场景内存降到九百兆左右,后台 CPU 接近零;窗口池上限后来开放为用户可调(默认六),照顾了两极用户。预算上线后的两个季度里,两次新功能导致的内存超标都在 CI 阶段被拦下修复。
解读:窗口池的设计本质是用有界的资源换取无界的体验——用户感觉想开多少开多少,实际常驻集合有硬上限。变式:如果产品的窗口间共享大量数据(比如多个窗口看同一份数据的不同视图),把数据上收到主进程单例、窗口只做视图,内存结构会进一步优化——这正好衔接第七章要讲的架构模式。
落地窗口池时还有一个用户沟通层面的细节:被销毁重建的窗口若带着未保存的编辑状态,重建后状态丢失会引发"刚才写的东西去哪了"的恐慌。对策是把窗口池的销毁动作与"状态已落盘"绑定——销毁前确保该窗口的数据已持久化(配合 7.2 节的本地存储层),宁可延缓销毁也不丢状态。资源策略与数据策略在这里咬合,单看任何一半都会设计出体验有洞的方案。
性能篇章收官,窗口又快又稳又轻。下一章进入交付环节:打包、签名、分发与自动更新。