首页 / Electron 入门教程 / 主进程与渲染进程

Electron 入门教程

主进程与渲染进程

本教程共 45 篇 · 第 4 篇 · 更新于 2026-08-03

Electron主进程渲染进程进程模型

4. 主进程与渲染进程

本节目标

  • 理解 Electron 为何采用多进程架构而非单进程。
  • 弄清主进程、渲染进程、Preload 三者的职责与关系。
  • 明白渲染进程默认没有 Node 能力的原因。
  • 理解为何跨进程交互必须依靠 IPC。
  • 认识 utility process 的用途与适用场景。

上一章你跑通了窗口,但可能还模糊:为什么 main.jsrequire,页面里却不行?这一章把 Electron 的进程模型讲透。理解了它,后面所有 API 的归属就清楚了。

4-1 为什么不是单进程

早期浏览器把所有标签页塞进一个进程。一个页面崩溃,整个浏览器跟着死。Chromium 把每个标签页拆成独立进程,单个页面出问题不会拖垮全局。

Electron 沿用了这套多进程思路。它把”管应用”和”显界面”两件事分到不同进程,既稳又能隔离风险。这也是它和早期 NW.js 最大的架构区别。

4-2 主进程:应用的入口与大脑

每个 Electron 应用有且仅有一个主进程,由 package.jsonmain 字段指定。它跑在 Node.js 环境里,能 require 任意模块、用全部 Node API。

主进程的核心职责有三类:

  • BrowserWindow 创建和管理窗口。
  • app 模块掌控应用生命周期。
  • 调用菜单、对话框、托盘这类原生桌面能力。
// 主进程 main.js
const { app, BrowserWindow } = require('electron/main')

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 800, height: 600 })
  win.loadFile('index.html')

  // webContents 让主进程能操作窗口里的网页内容
  win.webContents.on('did-finish-load', () => {
    console.log('页面加载完成')
  })
})

每个 BrowserWindow 实例都会附带一个独立的渲染进程。窗口被销毁时,对应的渲染进程也随之结束。

4-3 渲染进程:负责画界面

每个打开的 BrowserWindow 都会产生一个渲染进程。它做的事就是渲染网页内容,规则和你在浏览器里写前端完全一致。

因此界面、样式、交互逻辑,用的都是 HTML、CSS 和浏览器标准 JavaScript。你甚至可以用 webpack、React 这些前端工具链来组织代码。

<!-- 渲染进程入口就是普通网页 -->
<!DOCTYPE html>
<html>
  <head><meta charset="UTF-8" /><title>渲染进程</title></head>
  <body>
    <h1>这是渲染进程画的界面</h1>
  </body>
</html>

关键点来了:渲染进程默认拿不到 require,也用不了 Node API。历史上 Electron 曾默认开放,后来因安全问题关掉了。这正引出了 preload 的价值。

4-4 Preload:处在中间的桥梁

preload 脚本在页面内容加载前执行,运行在渲染进程上下文,却额外获准使用 Node API。它共享页面的 window 全局对象。

但直接往 window 挂变量是不行的,因为 contextIsolation 默认开启,preload 和页面处在隔离的上下文里。直接挂会拿不到:

// preload.js —— 不要这样写
window.myAPI = { desktop: true }
// renderer.js —— 拿到的是 undefined
console.log(window.myAPI) // => undefined

正确做法是用 contextBridge 安全暴露。隔离机制保证页面里的恶意代码碰不到 preload 的特权接口。

// preload.js —— 正确写法
const { contextBridge } = require('electron')

contextBridge.exposeInMainWorld('myAPI', {
  desktop: true
})
// renderer.js —— 现在能拿到了
console.log(window.myAPI) // => { desktop: true }

4-5 三者关系一张图

把三者摆在一起看:主进程是大脑,渲染进程是脸面,preload 是神经。主进程管窗口与系统,渲染进程画界面,preload 把两者受控地连起来。

主进程 (Node.js)
   │  创建 BrowserWindow

渲染进程 (网页)  ◀── preload 桥梁 ──▶ 主进程能力

一个窗口一个渲染进程,多个窗口就有多个渲染进程。它们彼此独立,互不影响,崩溃也只崩自己那一个。

4-6 为什么必须靠 IPC 通信

既然主进程和渲染进程是分开的,页面想做”读文件""弹对话框”这类事,就得请主进程代劳。两者之间的通信叫 IPC(Inter-Process Communication)。

渲染进程不能直接调主进程函数,主进程也不能直接改页面 DOM。所有跨进程的数据交换,都走 ipcRendereripcMain 这对通道。

// preload.js —— 暴露一个请求能力
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('electronAPI', {
  readConfig: () => ipcRenderer.invoke('read-config')
})
// main.js —— 主进程响应
const { ipcMain } = require('electron/main')
const fs = require('node:fs')
const path = require('node:path')

ipcMain.handle('read-config', () => {
  // 用 __dirname 拼绝对路径,别依赖启动时的工作目录
  return fs.readFileSync(path.join(__dirname, 'config.json'), 'utf-8')
})

这套”请求—响应”模式是 Electron 的通信主干。把特权操作收口在主进程,页面只拿到必要结果,安全边界就守住了。

4-7 还有个 utility process

除了主进程和渲染进程,Electron 还允许从主进程派生 utility process。它同样跑在 Node.js 环境,适合承载耗时计算、易崩溃组件,避免拖慢主进程。

它和 Node 的 child_process.fork 类似,但额外能跟渲染进程用 MessagePort 建立通道。普通应用用不到,了解有这个选项即可。

4-8 进程间边界意味着什么

隔离带来的直接后果是:主进程里的变量,渲染进程看不到;页面的 DOM,主进程改不了。两者各自独立,只能通过 IPC 传递可序列化的数据。

这意味着你不能把函数、DOM 节点这类对象直接跨进程传。IPC 通道只认能被结构化克隆的数据,比如字符串、数字、数组、普通对象。想传复杂对象,先把它压平成 JSON 友好的结构。

传不过去时报错信息往往含糊,排查思路很固定:把要传的值先 JSON.stringify 一遍试试,能序列化就基本能过桥,报错就说明里面混进了函数、类实例或循环引用。

4-9 常见误区

关于进程模型,最常见的误解是认为渲染进程能直接用 Node。在较早的 Electron 版本里这曾是默认行为,出于安全考虑早已关闭。现在的默认是渲染进程就是网页,特权操作全收口在主进程。

另一个误区是滥用 remote 思路。Electron 14 起 remote 模块已被移除,v43 里完全不存在。想从页面调主进程能力,正道是用 contextBridgeipcRenderer,不要去找 remote 的替代写法。

4-10 进程模型与稳定性

多进程带来的最大好处是隔离故障。某个渲染进程因为脚本错误卡死,不会拖垮主进程和其他窗口,用户关掉那个窗口即可恢复。

正因如此,把容易崩溃或计算密集的活儿放进独立进程是常见实践。Electron 提供的 utility process 就专为这类场景设计,它和主进程一样跑 Node,却不会在主进程里抢资源。

理解这套模型后,你写代码时会自然地把”界面”和”能力”分开:界面交给渲染进程,敏感的系统操作收口在主进程,中间用 preload 这座受控的桥连起来。

4-11 本章小结

Electron 是多进程架构:主进程管生命周期与原生能力,渲染进程画界面,preload 做受控桥梁。渲染进程默认没有 Node 能力,跨进程交互一律走 IPC。

记住隔离是好事,不是麻烦。下一章我们看 package.json 与脚本如何配置。