Electron 的 JS 代码分布在两个独立的进程中:主进程(Node.js Runtime)和渲染进程(Chromium V8 Runtime)。它们不是"一个 JS 环境",而是两个完全隔离的 OS 进程,通过 IPC 协作。
一、Electron 的进程架构模型
scss
┌─────────────────────────────────────────────────────────────┐
│ 操作系统 │
│ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ │ 主进程 (1个) │ │ 渲染进程 (N个) │ │
│ │ Main Process │ │ Renderer Process × N │ │
│ │ ┌───────────────┐ │ │ ┌─────────────────────┐ │ │
│ │ │ Node.js │ │ │ │ Chromium │ │ │
│ │ │ Runtime │ │◄──►│ │ ┌─────────────┐ │ │ │
│ │ │ (V8 + libuv) │ │IPC │ │ │ Blink 引擎 │ │ │ │
│ │ │ │ │ │ │ │ (DOM/CSS/JS)│ │ │ │
│ │ │ • 文件系统 │ │ │ │ └─────────────┘ │ │ │
│ │ │ • 网络请求 │ │ │ │ ┌─────────────┐ │ │ │
│ │ │ • 原生模块 │ │ │ │ │ V8 引擎 │ │ │ │
│ │ │ • 窗口管理 │ │ │ │ │ (隔离沙箱) │ │ │ │
│ │ │ • 系统API │ │ │ │ └─────────────┘ │ │ │
│ │ └───────────────┘ │ │ └─────────────────────┘ │ │
│ └─────────────────────┘ └─────────────────────────────┘ │
│ ▲ ▲ │
│ │ 预加载脚本 (Preload) │ │
│ └──────────── 桥梁 ──────────────┘ │
└─────────────────────────────────────────────────────────────┘
二、每个进程内部的线程模型
主进程(Main Process)
scss
┌─────────────────────────────────────┐
│ 主进程 (单个进程) │
│ ┌─────────────────────────────┐ │
│ │ Node.js Event Loop │ │
│ │ (libuv) │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ V8 │ │ 网络 │ │ 文件 │ │ │
│ │ │ 主线程│ │ IO │ │ IO │ │ │
│ │ └─────┘ └─────┘ └─────┘ │ │
│ │ ↓ 工作线程池 (libuv) │ │
│ └─────────────────────────────┘ │
│ • 单线程事件循环(同标准 Node.js) │
│ • 异步 IO 委托给 libuv 线程池 │
│ • 原生模块可能创建自己的线程 │
└─────────────────────────────────────┘
关键:主进程只有一个,是应用的入口和控制器。
渲染进程(Renderer Process)
scss
┌─────────────────────────────────────────┐
│ 渲染进程 (每个窗口一个) │
│ ┌─────────────────────────────────┐ │
│ │ Chromium 多线程架构 │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────────┐ │ │
│ │ │ Browser │ │ Renderer │ │ │
│ │ │ 线程 │ │ 线程 │ │ │
│ │ │ (主线程)│ │ (合成/绘制) │ │ │
│ │ └────┬────┘ └─────────────┘ │ │
│ │ │ │ │
│ │ ┌────┴────┐ ┌─────────────┐ │ │
│ │ │ V8 │ │ IO 线程 │ │ │
│ │ │ 主线程 │ │ (网络/存储) │ │ │
│ │ │ (执行JS) │ │ │ │ │
│ │ └─────────┘ └─────────────┘ │ │
│ │ │ │
│ │ • 还有 GPU 进程、扩展进程等 │ │
│ │ (Chromium 架构决定) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
关键:每个 BrowserWindow 创建一个独立的渲染进程。
三、你的 JS 代码到底跑在哪?
| 代码位置 | 运行在哪个 Runtime | 能做什么 | 不能做什么 |
|---|---|---|---|
main.js (入口) |
Node.js Runtime (主进程) | 文件系统、网络、创建窗口、系统API | 直接操作 DOM |
preload.js |
混合环境 (渲染进程,但有 Node.js 上下文) | require() 模块、ipcRenderer |
不能直接访问页面 DOM(有隔离) |
页面 JS (index.html 内) |
Chromium V8 (渲染进程,纯浏览器环境) | DOM 操作、Web API、调用 window.api |
require()、fs、os 等 Node 模块 |
四、预加载脚本(Preload)的关键角色
这是 Electron 架构中最容易被误解的部分:
javascript
┌─────────────────────────────────────────────┐
│ 渲染进程 │
│ ┌─────────────────────────────────────┐ │
│ │ 上下文隔离 (ContextIsolation) │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Preload │ │ 页面 JS │ │ │
│ │ │ 上下文 │ │ 上下文 │ │ │
│ │ │ │ │ │ │ │
│ │ │ • 有 Node.js│ │ • 纯浏览器 │ │ │
│ │ │ • 可 require│ │ • 无 require │ │ │
│ │ │ • 可 IPC │ │ • 只能 Web API│ │ │
│ │ └──────┬──────┘ └──────┬──────┘ │ │
│ │ │ │ │ │
│ │ └──── contextBridge ────────┘ │ │
│ │ (安全暴露 API) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
javascript
// preload.js --- 运行在渲染进程,但有 Node.js 能力
const { contextBridge, ipcRenderer } = require('electron')
// 把 ipcRenderer 的能力安全地暴露给页面 JS
contextBridge.exposeInMainWorld('electronAPI', {
readFile: (path) => ipcRenderer.invoke('fs:readFile', path),
onUpdate: (callback) => ipcRenderer.on('update', callback)
})
// 页面 JS 中:
// window.electronAPI.readFile('/path') → 发 IPC → 主进程处理 → 返回结果
关键洞察:
- Preload 和页面 JS 不在同一个 V8 上下文 ,但通过
contextBridge可以安全通信。 - 这是 Electron 的安全架构核心------防止不受信任的页面代码直接访问系统能力。
五、协作模型:消息流示例
javascript
用户点击按钮 (渲染进程)
│
▼
页面 JS: window.electronAPI.saveFile(data)
│
▼
Preload: ipcRenderer.invoke('dialog:save', data) ──┐
│ │
▼ │
V8 序列化消息 ──────► Chromium IPC 管道 ────────────┤
│ │
▼ │
操作系统内核 IPC ─────► 主进程接收 │
│ │
▼ │
主进程: ipcMain.handle('dialog:save', async (e, data) => {
const { filePath } = await dialog.showSaveDialog()
await fs.writeFile(filePath, data)
return { success: true }
})
│
▼
结果反向通过 IPC 返回 ──► Preload ──► 页面 JS Promise resolve
六、与 React Native 的对比(回应你之前的类比)
| 维度 | React Native | Electron |
|---|---|---|
| JS 运行位置 | 单个进程内的 JS 引擎(Hermes/JSC) | 两个独立进程(主进程 Node.js + 渲染进程 Chromium V8) |
| 原生通信 | JSI / Bridge(同进程内存共享或异步) | IPC(跨进程消息,必然序列化) |
| JS 线程数 | 1 个 JS 线程 + 原生模块线程 | 主进程 1 个 V8 + 每个窗口 1 个 V8 |
| 渲染 | Yoga 布局 + 平台原生组件 | Chromium Blink 引擎直接渲染 |
| 安全模型 | 相对宽松(同进程) | 严格进程隔离 + ContextIsolation |
七、一句话总结
Electron 的 JS 代码"分身"运行在两个世界:主进程是"拥有系统权限的 Node.js 后端",渲染进程是"被沙箱隔离的 Chromium 前端"。它们通过 IPC 像微服务一样协作,预加载脚本是两者之间的安全网关。这不是"一个 JS 环境",而是"两个独立运行时通过消息协议协作"的分布式架构。