:::success 知识点

  1. 理解主进程是什么
  2. 理解渲染进程是什么
  3. 掌握文中提到的进程间通信的方式(3.3、3.4)
  4. 掌握在 electron 中打开调试工具的多种方式
  5. 掌握在 electron 中刷新页面的方式
  6. 了解 remote 模块被抛弃的原因
  7. 理解序列化和反序列化 :::

:::warning 备注
前言中提及的这些知识点非常重用
概念:对于概念(主进程、渲染进程)的理解是一个循序渐进的过程,随着不断地学习,理解会逐步深入。
实操:IPC 通信,这可以说是 Electron 中最为重要的知识点之一,实际工作中很常用。

remote 模块:
书中提供的 demo 用到了 remote 模块,虽然使用这玩意儿看似可以让我们在 electron 的渲染进程中访问主进程的模块,但是这东西在实际开发中可以说是 禁用 的,一方面及其影响性能,另一方面,很容易出现一些意想不到的 bug。
起初,remote 模块也是 electron 的核心模块之一,但是官方很快就把 remote 模块从 electron 中给移除了,现在如果想要在 electron 中引入 remote 模块需要引入 @electron/remote 包。 :::

打开调试工具

通过快捷方式打开

  • windows:Ctrl + Shift + I
  • mac:Option + Command + I

image.png

通过默认菜单打开

除了使用上述的快捷键的方式来打开调试窗口,也可以通过窗口的默认菜单:View -> Toggle Developer Tools 来打开调试工具。

image.png

虽说使用默认菜单也可以打开渲染进程的调试工具,但是作者还是 推荐记住快捷键。这是因为默认的窗口菜单对于用户来说,意义不大,我们 在实际开发时,通常都会将 Electron 给我们提供的窗口菜单给禁用掉

通过代码打开

如果希望在项目启动的时候就立刻打开开发者工具,那么可以在主进程 win 对象 loadFile(或 loadURL)之后,使用代码 win.webContents.openDevTools() 打开该工具。

webContents 是 Electron 中一个非常重要的对象,负责渲染和控制页面,后续会详细介绍该对象。

刷新页面

刷新页面的方式和浏览器上有所不同,浏览器上可以重新输入一遍 URL 来实现刷新,但是 Electron 应用,它没有给我们提供地址栏,所以这种重新输入 URL 的方式,在 Electron 中是没法用的。

但是,可以使用快捷键的方式来刷新页面:

  • windows:Ctrl + R
  • mac:Command + R

主进程的日志打印位置和渲染进程的日志打印位置

  • 主进程 打印的内容,得在 终端 中查看
  • 渲染进程 打印的内容,得在 渲染进程页面的调试工具(chrome 开发者工具) 中查看

在 Electron 应用中,主进程和渲染进程的日志位置是不同的。这是由 Electron 的内部工作原理决定的。

首先,要明白 Electron 是基于 Chromium 和 Node.js 构建的,其结构分为两个主要的进程类型:主进程(main process)和渲染进程(renderer process)。

  1. 主进程:这是 Electron 应用的入口点,它运行 package.json 文件中定义的 main 脚本,并且在应用生命周期中只能有一个。它可以显示应用的 GUI,并且可以创建更多的渲染进程。主进程的日志输出位置通常在你运行 Electron 应用的终端或控制台中,这是因为主进程在 Electron 应用启动时运行,它与你的系统 shell 直接交互
  2. 渲染进程:每一个 Electron 的 BrowserWindow 实例都在它自己的渲染进程中运行。这些进程负责运行显示用户界面的 web 页面。因为 Electron 使用了 Chromium 来渲染 web 页面,所以每个渲染进程都相当于一个 Chromium 浏览器标签页。这些渲染进程的日志输出位置通常在每个渲染进程的开发者工具控制台(DevTools console)中

这种主进程和渲染进程的分离方式是为了提高 Electron 应用的稳定性和安全性。如果所有的代码都在一个进程中运行,那么一旦有任何代码出现错误并导致进程崩溃,整个应用都会立即崩溃。而通过在不同的进程中运行代码,可以实现隔离,保证一部分代码出现问题时,不会影响到其他的进程。此外,这也有助于实现安全性控制,防止不受信任的代码访问敏感的系统资源。

这种区分主要是基于 Chromium 的多进程架构,它允许每个标签页(或在 Electron 中,每个网页)在其自己的进程中运行。这有助于提高稳定性(一个页面崩溃不会影响其他页面)和安全性(网页不能访问其他网页的数据,或访问操作系统的功能)。

因此,这两种进程的日志位置不同,是因为它们在 Electron 的内部架构中有着不同的角色和职责,分别运行在不同的环境中,并与不同的系统部分进行交互。

谈一谈 remote 模块

remote 这个模块吧,极度尴尬,也许 Electron 开发团队暴露出这个 API 的初衷是好的,但是它身上的问题实在太多。很多大佬都建议,remote 模块呀,能不用就不用

Electron 的 remote 模块起初被设计为提供一种方便的方式,使得在渲染进程(renderer process)中可以访问主进程(main process)的模块。但是,实际上,remote 模块在使用过程中遇到了很多问题。

  1. 性能问题:remote 模块工作方式是,每次通过它进行进程间通信(IPC)时,都会阻塞渲染进程直到主进程响应。这种方式在处理大量或复杂的 IPC 调用时,会导致应用性能下降。
  2. 可维护性和可理解性:remote 模块的使用方式可能会给开发者造成一种误解,即主进程和渲染进程可以无缝交互,而实际上这背后涉及到的进程间通信非常复杂。这可能导致开发者对 Electron 应用的实际工作方式理解不深,也使得 debug 变得困难。
  3. 安全问题:remote 模块的设计使得渲染进程能够访问主进程的模块,这有可能被恶意代码利用,从而导致安全隐患。

基于以上原因,Electron 团队在 2020 年决定废弃 **remote** 模块,并在 Electron 12 版本中移除。为了不破坏已有的项目,Electron 提供了 **@electron/remote** 这个单独的包,但是也明确表示,尽量避免在新的项目中使用它,或者从旧的项目中逐步移除它。

替代 remote 模块的更推荐的方式是直接使用 ipcMainipcRenderer 进行明确的、双向的进程间通信。

使用 ipcMainipcRenderer 替代 remote 模块具有以下好处:

  1. 性能提升:因为 ipcMainipcRenderer 不会阻塞渲染进程直到主进程响应,所以它们可以提高应用的性能,特别是在进行大量或者复杂的进程间通信时。
  2. 更好的可维护性和可理解性:通过显式定义每个进程间的通信,ipcMainipcRenderer 模块可以提高应用的可维护性和可理解性。它们让开发者清楚地理解什么操作发生在主进程,什么操作发生在渲染进程,而且这有助于在出现问题时更容易进行 debug。
  3. 增强应用的安全性ipcMainipcRenderer 通过明确定义进程间的通信,减小了渲染进程访问主进程的可能性,从而可以减少潜在的安全风险。

因此,ipcMainipcRenderer 提供了更灵活、高效的方式来进行进程间通信。

使用 Electron 的 remote 模块的操作可能会导致渲染进程阻塞,尤其是在对主进程进行重要或复杂操作的时候,因为它是一种 同步 的操作方式。

**remote** 模块是同步的,当你在渲染进程中调用 **remote** 模块访问主进程的对象时,Electron 实际上会在后台将这个请求发送到主进程,然后等待主进程完成请求并返回结果。在此过程中,渲染进程将会被阻塞,直到主进程返回结果。在此期间,渲染进程不能进行其他操作,因此对性能的影响可能会很大。这就是为什么使用 **remote** 模块可能会导致性能问题,尤其是在进行大量或者复杂的主进程访问时。

然而请注意,不是所有使用 remote 的操作都会引起显著的阻塞或性能问题,这完全取决于你在主进程中通过 remote 模块调用的具体操作。如果这些操作非常快速,并且不涉及大量的计算或 I/O,则阻塞可能并不明显。但是,如果你在主进程中执行长时间运行或者计算密集型的操作,那么渲染进程可能会明显阻塞,影响应用的性能和响应性。

另外,由于 remote 模块使得主进程的对象能够在渲染进程中被直接访问和操作,这会增加一些潜在的安全风险。因为如果渲染进程被恶意脚本或者第三方库控制,那么它们就能够直接访问和操作主进程的对象。

因此,现在一般推荐使用 **ipcRenderer****ipcMain** 模块进行进程间的通信,而避免使用 **remote** 模块

:::info 小 知 识

如果某个模块,我们用起来,感觉它确实很好使,比如 remote 模块。但是呢,这个模块存在很多问题,一旦使用不当,造成的后果会及其严重,那么这时候,官方的做法一般都是:添加阻碍 —— 就是让我们没那么容易使用它。

Electron 官方的行为(让 remote 模块不再那么容易被引入,增加它的使用成本)也证实了这一点,起初的 Electron 版本,我们可以直接在渲染进程中引入 remote 模块,然后直接使用,非常方便。但是呢,如果是在最新的 Electron 版本中,我们压根是没法使用的,想要使用 remote 模块,还得做不少额外操作。

包括 react 框架中,也存在类似的东西,比如它的 dangerouslySetInnerHTML 这个 API,其实就是用来设置 innerHTML 的。但是,实际用过的人应该都知道,它写起来会非常别扭,这其实就可以理解为,是官方在提醒开发者,我都如此设计这个 API 了,刻意让它写起来麻烦一些,可是你还用,那么说明你确实需要该功能,想必你也清楚使用不当的后果了,如果出问题了,可别甩锅给我。

:::

序列化和反序列化

无论是渲染进程给主进程发送消息,还是主进程给渲染进程发送消息,其背后的实现原理都是 进程间通信(IPC)。此通信过程中,消息发送的 json 对象会被序列化和反序列化,所以 json 对象中包含的方法和原型链上的数据不会被传递

因此,我们就不要异想天开地把 app、BrowserWindow 这样的模块作为通信的变量来传递了,即便渲染进程可以收到,那也是没法用的。

🤔 什么是序列化、反序列化?

序列化

序列化是将数据结构或对象状态转换为可以存储或传输的格式的过程。在序列化期间,对象的属性和数据被转换为字节流或字节序列,这样可以更容易地写入文件、存储在数据库中,或者在网络中传输。

API:JSON.stringify

反序列化

反序列化是序列化过程的反向操作,即从字节流或字节序列中恢复对象的状态和数据。这使得数据或对象可以在不同的环境或运行时实例之间持久化和传输。

API:JSON.parse

小结

在 JavaScript 中,我们通常使用 JSON.stringify 来进行序列化,使用 JSON.parse 来进行反序列化。序列化的结果是一个 JSON 格式的字符串,可以方便地在网络中传输或存储在文件或数据库中。

但是需要注意的是,JSON.stringify 只能序列化对象的可枚举的自有属性,即那些直接属于对象(不在原型链上)、且可被 for…in 循环枚举的属性。函数、undefined 和 Symbol 值不能被序列化。同样地,对象的原型链、getter 和 setter 函数也不能被序列化。

因此,在 Electron 的进程间通信中,你不能直接发送包含这些类型的对象。如果你试图发送一个包含函数或 undefined 的对象,这些值将会在序列化过程中被忽略。同样,对象的原型链也不会被发送。这就是为什么你不能直接发送 Electron 的 app 或 BrowserWindow 对象,因为它们包含函数和原型链。

所以在进行进程间通信时,通常我们只传输数据,而不是对象或函数。对于需要在多个进程之间共享的复杂操作或行为,我们可以设计特定的 IPC 消息或事件,而不是试图直接发送对象或函数。