Skip to content

一句话结论

主进程里:JavaScript 通过 V8 调用「启动时已注册的 C++ 函数」,间接使用操作系统能力。
渲染进程里(Vue):默认不是同一个 V8、也不是完整 Node;要碰系统得通过 IPC 找主进程。

下面按「先分清两套运行时 → 再理解主进程怎么过桥 → 最后看 Vue 怎么间接用能力」的顺序讲。


一、最容易混的一点:不是同一个 Node,也不是同一个 V8

很多人以为 main.cjs 和 Vue 打包后的 JS「都在 Electron 里跑,应该是同一个引擎」。其实不是。

1.1 两套 JS,跑在两个进程里

text
┌──────────── 主进程 ────────────┐     ┌────────── 渲染进程 ──────────┐
│  electron/main.cjs             │     │  Vue 打包后的 JS(dist)        │
│  Node 运行时 ✅                 │     │  Node 运行时 ❌(通常已关闭)   │
│  require('electron') ✅        │     │  更像 Chrome 里跑网页           │
│  require('fs') ✅              │     │  只有 Web API + 可选 preload   │
│  自己的 V8 isolate             │     │  自己的 V8 isolate             │
└────────────────────────────────┘     └────────────────────────────────┘
              │                                        │
              └──────────── IPC(跨进程消息)─────────────┘
代码跑在哪环境
main.cjs主进程完整 Node + Electron
Vue 业务代码BrowserWindow 里的渲染进程Chromium(接近浏览器)

配置了 nodeIntegration: false 时,Vue 里不能main.cjs 那样随便 require('fs')

1.2 两个 V8 一样吗?——不一样

主进程渲染进程(Vue)
进程1 个 Electron 主进程每个窗口至少 1 个 Chromium 渲染进程
V8Node 用的那份网页 用的那份(在 Chromium 里)
是否同一个堆/上下文❌ 隔离的 两个(或多个)V8 isolate

类比:

  • 主进程 V8 ≈ 后台服务里的 JS 引擎
  • 渲染进程 V8 ≈ Chrome 打开一个标签页里的 JS 引擎

不是「一个 V8 跑两份代码」,而是 两个进程各有一个 V8
main.cjs 的 JS 和 Vue 的 JS 不会跟同一个 V8 实例交互。

1.3 两个 Node 一样吗?——也不一样

更准确地说:只有主进程那份算「在 Node 里」。

  • 主进程:Electron 内置 Node,跑 main.cjs
  • 渲染进程(现代默认配置):没有把完整 Node 暴露给 Vue;页面 JS 由 Chromium 执行,环境接近浏览器。

Vue 里的 JS 不是main.cjs 共用同一个 Node 进程里的 Node API。

1.4 为什么都是 JavaScript,却感觉像「都在 Electron 里」?

因为 Electron 这个桌面应用 同时带了:

  • 一套 Node 主进程(管窗口、文件、子进程)
  • 一套 Chromium(显示 Vue 页面)

对外是一个 App,对内是 多进程

text
electron 可执行文件
├── 主进程:Node + V8 + main.cjs
└── 渲染进程:Chromium + V8 + Vue(无完整 Node)

window.loadURL(...) 的作用:主进程让 Chromium 另起一个渲染进程 去加载你的页面。

1.5 三个问题,一张表

问题答案
main.cjs 和 Vue 会跟同一个 V8 交互吗?不会,各进程各有一个 V8
Vue 的 JS 也在 Node 里跑吗?默认不会,在 Chromium 渲染进程里跑
两个 Node 一样吗?不一样;实质是主进程有 Node,Vue 侧没有完整 Node

后面说的「绑定好了,JS 一调就进 C++」——对主进程的 main.cjs 成立对 Vue 页面,默认只有浏览器那套(DOM、fetch 等),要碰系统得 IPC 找主进程


二、主进程里:JS 怎么碰到系统

2.1 调用链路(心智模型)

不是「JS 被编译成 C++ 指令」,而是一次跨语言函数调用:

text
你写的 JS(require('fs').readFile、new BrowserWindow())

V8 执行,遇到「原生绑定」(Native Binding)

跳进 Electron / Node 里用 C++ 写好的函数

C++ 调操作系统 API

结果包装成 JS 值,还给 V8 / 你的代码
text
  ┌─────────────┐     原生绑定层      ┌─────────────┐     系统 API     ┌──────────┐
  │  你的 JS    │ ─────────────────→ │  C++ 实现   │ ──────────────→ │  操作系统  │
  │  (V8 执行)  │ ←───────────────── │ (Node/Electron)│ ←────────────── │          │
  └─────────────┘   返回值转成 JS 值   └─────────────┘                   └──────────┘
层级职责
V8解析、执行 JavaScript
C++ 绑定层注册「JS 函数 ↔ C++ 函数」的桥(fsBrowserWindow…)
操作系统真正的文件、窗口、进程、网络

记法:运行时桥接,不是 编译期翻译

说法对不对
readFile 时跳到同进程里的 C++ 函数
整份业务 JS 先编译成 C++ 机器码再跑

2.2 事先注册好的「插座」

C++ 绑定像 开机时已装好的插座

  1. Electron / Node 启动时,C++ 向 V8 注册原生函数(fs.readFileBrowserWindowapp.whenReady…)。
  2. JS 里它们看起来像普通对象、方法。
  3. 一调用,V8 直接跳进已注册的 C++,不会把整段业务 JS「翻译成 C++」。
  4. C++ 完成后,把返回值变回 JS 类型。

绑定事先写好 → JS 一调 → 就等于执行那段 C++。仅主进程这条链路。)

自写 function foo() {}fs.readFile 等原生 API
谁执行V8 跑 JSV8 转调已注册 C++
能否碰系统不能(除非再调别的 API)
要不要注册不需要必须

只有被注册过的 API 才能过桥;自写函数不会自动变 C++。

2.3 主进程示例

js
// main.cjs(主进程)
const { app, BrowserWindow } = require('electron')
const fs = require('fs')

app.whenReady().then(() => {
  const win = new BrowserWindow({ width: 800, height: 600 })
  // new BrowserWindow() → C++ 绑定 → 系统创建窗口
})
text
main.cjs → 主进程 V8 → 命中 C++ 绑定 → 系统 API → 返回 JS 对象

全程在主进程一个进程内完成,对你来说就是「调了一个 JS 函数」。


三、渲染进程里:Vue 怎么间接用系统能力

渲染进程里跑的是 Chromium + VuenodeIntegration: false 时:

  • 不能随意 require('fs')、不能 new BrowserWindow()
  • 页面 JS 由 渲染进程自己的 V8 执行,但没有主进程那排 Node/Electron「插座」

3.1 标准路径:IPC

不能指望「和 main 共用同一个 V8/Node」,而是:

text
Vue(渲染进程 V8)
  → ipcRenderer.invoke('run-agent', ...)
  → 主进程 ipcMain(主进程 V8 + Node)
  → fs.readFile / child_process / BrowserWindow …
  → 结果经 IPC 回传 Vue
进程能否直接走 C++ 绑定典型方式
主进程require('electron')fs
渲染进程❌(默认)preload + contextBridge + IPC

主进程:同进程函数调用,直接插「插座」。
渲染进程:发消息,请主进程代插「插座」。

3.2 preload:窄口子,不是第二套 Node

preload 仍在渲染进程,在页面加载前执行,用 contextBridge 向页面暴露白名单 API(如 window.api.readFile)。

text
Vue 调用 window.api.readFile(path)
  → preload 里封装的 ipcRenderer.invoke
  → 主进程 fs.readFile(C++ 绑定 → 系统)

preload 不会在渲染进程再注册一套 C++ 绑定;只是把 IPC 包成好用的 JS 函数。


四、总览图

mermaid
flowchart TB
  subgraph renderer [渲染进程:独立 V8,无完整 Node]
    Vue[Vue / 页面 JS]
    Preload[preload + contextBridge]
    Vue --> Preload
  end

  subgraph main [主进程:独立 V8 + Node]
    MainJS[main.cjs]
    Bindings[启动时注册的 C++ 绑定]
    MainJS --> Bindings
  end

  subgraph os [操作系统]
    SysAPI[文件 / 窗口 / 进程 API]
  end

  Preload -->|IPC| MainJS
  Bindings --> SysAPI
  SysAPI --> Bindings
  Bindings -->|JS 值| MainJS
  MainJS -->|IPC 结果| Preload

五、自检清单

进程与运行时

  • [ ] 能说清主进程、渲染进程是两个进程、两个 V8
  • [ ] 知道 Vue 默认不在完整 Node 里跑
  • [ ] 知道 main.cjs 与 Vue 不共用同一个 V8 堆

主进程与绑定

  • [ ] 能说清 V8、C++ 绑定、系统 API 各管什么
  • [ ] 知道绑定是启动时注册的「插座」,调用时跳进 C++
  • [ ] 知道自写 function 不会变 C++;只有已注册 API 能过桥
  • [ ] 不会把「JS 调系统」理解成「JS 编译成 C++」

渲染进程与 IPC

  • [ ] 知道渲染进程默认要靠 IPC 让主进程调系统 API
  • [ ] 知道 preload 是 IPC 的薄封装,不是第二套 Node

六、小结

维度主进程(main.cjs渲染进程(Vue)
V8主进程里、给 Node 用Chromium 里、给网页用
Node✅ 完整 Node + Electron❌ 通常关闭 nodeIntegration
调系统JS → 已注册 C++ → 系统 APIIPC → 主进程再调 C++
记忆「插座」在主进程墙上通过 IPC 请主进程插电

学 Electron 桌面端,建议顺序:

  1. 先分清 两套 V8 / 两套进程(本章第一节)
  2. 再理解主进程 注册 + 跳转(第二节)
  3. 最后看 IPC、preload、contextIsolation 如何让 Vue 安全地用主进程能力(第三、四节)

把这三层串起来,后面读项目里的 main.cjspreload.ts、Vue 发 IPC 的代码会顺很多。

本站总访问