XCel 项目总结:Electron 与 Vue 的性能优化

Excel 的列转换

  • Excel 的列需要用『字母』表示,但不能简单地通过
    String.fromCharCode()
    实现,因为当超出 26 列 时就会产生问题(如:第 27
    列,String.fromCharCode(65+26) 得到的是 [,而不是
    AA)。因此,这需要通过『十进制和 26 进制转换』算法来实现。

JavaScript

// 将传入的自然数转换为26进制表示。映射关系:[0-25] -> [A-Z]。
function getCharCol(n) { let temCol = ”, s = ”, m = 0 while (n >=
0) { m = n % 26 + 1 s = String.fromCharCode(m + 64) + s n = (n – m) / 26
} return s }

1
2
3
4
5
6
7
8
9
10
11
12
13
// 将传入的自然数转换为26进制表示。映射关系:[0-25] -> [A-Z]。
function getCharCol(n) {
  let temCol = ”,
    s = ”,
    m = 0
  while (n >= 0) {
    m = n % 26 + 1
    s = String.fromCharCode(m + 64) + s
    n = (n – m) / 26
  }
  return s
}
 

JavaScript

// 将传入的26进制转换为自然数。映射关系:[A-Z] ->[0-25]。
function getNumCol(s) { if (!s) return 0 let n = 0 for (let i = s.length

  • 1, j = 1; i >= 0; i–, j *= 26) { let c = s[i].toUpperCase() if
    (c < ‘A’ || c > ‘Z’) return 0 n += (c.charCodeAt() – 64) * j }
    return n – 1 }
1
2
3
4
5
6
7
8
9
10
11
12
// 将传入的26进制转换为自然数。映射关系:[A-Z] -&gt;[0-25]。
function getNumCol(s) {
  if (!s) return 0
  let n = 0
  for (let i = s.length – 1, j = 1; i &gt;= 0; i–, j *= 26) {
    let c = s[i].toUpperCase()
    if (c &lt; ‘A’ || c &gt; ‘Z’) return 0
    n += (c.charCodeAt() – 64) * j
  }
  return n – 1
}
 

主进程与渲染进程的区别

主进程使用 BrowserWindow 实例创建页面。每个 BrowserWindow
实例都在自己的渲染进程里运行页面。当一个 BrowserWindow
实例被销毁后,相应的渲染进程也会被终止。

主进程管理所有页面和与之对应的渲染进程。每个渲染进程都是相互独立的,并且只关心他们自己的页面。

由于在页面里管理原生 GUI
资源是非常危险而且容易造成资源泄露,所以在页面调用 GUI 相关的 APIs
是不被允许的。如果你想在网页里使用 GUI
操作,其对应的渲染进程必须与主进程进行通讯,请求主进程进行相关的 GUI
操作。

在 Electron,我们提供几种方法用于主进程和渲染进程之间的通讯。像
ipcRenderer
ipcMain
模块用于发送消息, remote
模块用于 RPC 方式通讯。这些内容都可以在一个 FAQ 中查看 how to share
data between web
pages

斜分割线

如图:网赌平台哪个信誉好 1

分割线可以通过 ::after/::before 伪类元素实现一条直线,然后通过
transform:rotate();
旋转特定角度实现。但这种实现的一个问题是:由于宽度是不定的,因此需要通过
JavaScript 运算才能得到准确的对角分割线。

因此,这里可以通过 CSS 线性渐变
linear-gradient(to top right, transparent, transparent calc(50% - .5px), #d3d6db calc(50% - .5px), #d3d6db calc(50% + .5px), transparent calc(50% + .5px))
实现。无论宽高如何变,依然妥妥地自适应。

参照下面例子

复制并且运行这个库
electron/electron-quick-start

注意:运行时需要你的系统已经安装了
Git
Node.js(包含
npm)。

# 克隆这仓库
$ git clone https://github.com/electron/electron-quick-start
# 进入仓库
$ cd electron-quick-start
# 安装依赖库并运行应用
$ npm install && npm start

更多 apps 例子,查看 electron 社区创建的 list of
boilerplates

Vue 全家桶

该工具使用了 Vue、Vuex、Vuex-router。在工具基本定型阶段,由 1.x 升级到了
2.x。

主进程

在 Electron 里,运行 package.jsonmain
脚本的进程被称为主进程。在主进程运行的脚本可以以创建 web
页面的形式展示 GUI。

两个进程(重点)

Electron
有两种进程:『主进程』和『渲染进程』。部分模块只能在两者之一上运行,而有些则无限制。主进程更多地充当幕后角色,而渲染进程则是应用程序的各个窗口。

注:可通过任务管理器(PC)/活动监视器(Mac)查看进程的相关信息。

  • 模块:Electron 的 API 是根据它们的用途进行分组。例如:dialog
    模块拥有所有原生 dialog 的 API,如打开文件、保存文件和警告等弹窗。

渲染进程

由于 Electron 使用 Chromium 来展示页面,所以 Chromium
的多进程结构也被充分利用。每个 Electron
的页面都在运行着自己的进程,这样的进程我们称之为渲染进程

在一般浏览器中,网页通常会在沙盒环境下运行,并且不允许访问原生资源。然而,Electron
用户拥有在网页中调用 Node.js 的 APIs
的能力,可以与底层操作系统直接交互。

项目背景

用户研究的定量研究和轻量级数据处理中,均需对数据进行清洗处理,以剔除异常数据,保证数据结果的信度和效度。目前因调研数据和轻量级数据的多变性,对轻量级数据清洗往往采取人工清洗,缺少统一、标准的清洗流程,但对于调研和轻量级的数据往往是需要保证数据稳定性的,因此,在对数据进行清洗时最好有标准化的清洗方式。

打造你第一个 Electron 应用

大体上,一个 Electron 应用的目录结构如下:

your-app/
├── package.json
├── main.js
└── index.html

package.json 的格式和 Node 的完全一致,并且那个被 main
字段声明的脚本文件是你的应用的启动脚本,它运行在主进程上。你应用里的
package.json 看起来应该像:

{
  "name"    : "your-app",
  "version" : "0.1.0",
  "main"    : "main.js"
}

注意:如果 main 字段没有在 package.json 声明,Electron会优先加载
index.js

main.js 应该用于创建窗口和处理系统事件,一个典型的例子如下:

const {app, BrowserWindow} = require('electron')
const path = require('path')
const url = require('url')

// 保持一个对于 window 对象的全局引用,如果你不这样做,
// 当 JavaScript 对象被垃圾回收, window 会被自动地关闭
let win

function createWindow () {
  // 创建浏览器窗口。
  win = new BrowserWindow({width: 800, height: 600})

  // 加载应用的 index.html。
  win.loadURL(url.format({
    pathname: path.join(__dirname, 'index.html'),
    protocol: 'file:',
    slashes: true
  }))

  // 打开开发者工具。
  win.webContents.openDevTools()

  // 当 window 被关闭,这个事件会被触发。
  win.on('closed', () => {
    // 取消引用 window 对象,如果你的应用支持多窗口的话,
    // 通常会把多个 window 对象存放在一个数组里面,
    // 与此同时,你应该删除相应的元素。
    win = null
  })
}

// Electron 会在初始化后并准备
// 创建浏览器窗口时,调用这个函数。
// 部分 API 在 ready 事件触发后才能使用。
app.on('ready', createWindow)

// 当全部窗口关闭时退出。
app.on('window-all-closed', () => {
  // 在 macOS 上,除非用户用 Cmd + Q 确定地退出,
  // 否则绝大部分应用及其菜单栏会保持激活。
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('activate', () => {
  // 在这文件,你可以续写应用剩下主进程代码。
  // 也可以拆分成几个文件,然后用 require 导入。
  if (win === null) {
    createWindow()
  }
})

// 在这文件,你可以续写应用剩下主进程代码。
// 也可以拆分成几个文件,然后用 require 导入。

最后,你想展示的 index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    We are using node <script>document.write(process.versions.node)</script>,
    Chrome <script>document.write(process.versions.chrome)</script>,
    and Electron <script>document.write(process.versions.electron)</script>.
  </body>
</html>

为什么它如此重要?

通常来说,每个操作系统的桌面应用都由各自的原生语言进行编写,这意味着需要
3 个团队分别为该应用编写相应版本。而 Electron 则允许你用 Web
语言编写一次即可。

  • 原生(操作系统)语言:用于开发主流操作系统应用的原生语言的对应关系(大多数情况下):Mac
    对应 Objective C、Linux 对应 C、Windows 对应 C++。

electron-prebuilt

electron
是一个 npm 模块,包含所使用的 Electron 预编译版本。
如果你已经用 npm 全局安装了它,你只需要按照如下方式直接运行你的应用:

electron .

如果你是局部安装,那运行:

为 Electron 应用生成 Windows 安装包

通过 electron-builder 可直接生成常见的
MacOS 安装包,但它生成的 Windows 的安装包却略显简洁(默认选项时)。

网赌平台哪个信誉好 2
Mac 常见的安装模式,将“左侧的应用图标”拖拽到“右侧的 Applications”即可

通过 electron-builder 生成的 Windows 安装包与我们在 Windows
上常见的软件安装界面不太一样,它没有安装向导和点击“下一步”的按钮,只有一个安装时的
gif 动画(默认的 gif 动画如下图,当然你也可以指定特定的 gif
动画),因此也就关闭了用户选择安装路径等权利。

网赌平台哪个信誉好 3
Windows 安装时 默认显示的 gif
动画

如果你想为打包后的 Electron 应用(即通过
electron-packager/electron-builder
生成的,可直接运行的程序目录)生成拥有点击“下一步”按钮和可让用户指定安装路径的常见安装包,可以尝试
NSIS 程序,具体可看这篇教程 《[教學]只要10分鐘學會使用 NSIS
包裝您的桌面軟體–安裝程式打包。完全免費。》

注:electron-builder
也提供了生成安装包的配置项,具体查看>>

NSIS(Nullsoft Scriptable Install System)是一个开源的 Windows
系统下安装程序制作程序。它提供了安装、卸载、系统设置、文件解压缩等功能。正如其名字所描述的那样,NSIS
是通过它的脚本语言来描述安装程序的行为和逻辑的。NSIS
的脚本语言和常见的编程语言有类似的结构和语法,但它是为安装程序这类应用所设计的。

至此,CSS、JavaScript 和 Electron 相关的知识和技巧部分阐述完毕。


Linux

$ ./electron/electron your-app/

记得关闭 Vuex 的严格模式

另外,由于自己学艺不精和粗心大意,忘记在生产环境关闭 Vuex
的『严格模式』。

Vuex 的严格模式要在生产环境中关闭,否则会对 state 树进行一个深观察
(deep
watch),产生不必要的性能损耗。也许在数据量少时,不会注意到这个问题。

还原当时的场景:导入 Excel 数据后,再进行交互(涉及 Vuex
的读写操作),需要等几秒才会响应,而直接通过纯 DOM
监听的事件则无此问题。由此,判断出是 Vuex 问题。

JavaScript

const store = new Vuex.Store({ // … strict: process.env.NODE_ENV !==
‘production’ })

1
2
3
4
5
const store = new Vuex.Store({
  // …
  strict: process.env.NODE_ENV !== ‘production’
})
 

macOS

$ ./Electron.app/Contents/MacOS/Electron your-app/

Electron.app 里面是 Electron 发布包,你可以在
这里
下载到。

关于作者:刘健超-J.c

网赌平台哪个信誉好 4

前端,在路上…http://jchehe.github.io
个人主页
·
我的文章
·
19
·
    

网赌平台哪个信誉好 5

手工下载 Electron 二进制文件

如果你手工下载了 Electron
的二进制文件,你也可以直接使用其中的二进制文件直接运行你的应用。

技术选型

  • Electron:桌面端跨平台框架,为 Web
    提供了原生接口的权限。打包后的程序兼容 Windows 7 及以上、Mac、Linux
    的 32 / 64 位系统。详情>>
  • Vue 全家桶:Vue
    拥有数据驱动视图的特性,适合重数据交互的应用。详情>>
  • js-xlsx:兼容各种电子表格格式的解析器和生成器。纯 JavaScript
    实现,适用于 Node.js 和 Web
    前端。详情>>

Windows

$ .\electron\electron.exe your-app\

内存占有量过大

解决了执行效率和渲染问题后,发现也存在内存占用量过大的问题。当时猜测是以下几个原因:

  1. 三大耗时操作均放置在 background process
    处理。在通讯传递数据的过程中,由于不是共享内存(因为 IPC 是基于
    Socket
    的),导致出现多份数据副本(在写这篇文章时才有了这相对确切的答案)。
  2. Vuex
    是以一个全局单例的模式进行管理,但它会是不是对数据做了某些封装,而导致性能的损耗呢?
  3. 由于 JavaScript
    目前不具有主动回收资源的能力,所以只能主动对闲置对象设置为
    null,然后等待 GC 回收。

由于 Chromium 采用多进程架构,因此会涉及到进程间通信问题。Browser
进程在启动 Render 进程的过程中会建立一个以 UNIX Socket 为基础的 IPC
通道。有了 IPC 通道之后,接下来 Browser 进程与 Render
进程就以消息的形式进行通信。我们将这种消息称为 IPC
消息,以区别于线程消息循环中的消息。
——《Chromium的IPC消息发送、接收和分发机制分析》

定义:为了易于理解,以下『Excel 数据』均指 Excel 的全部有效单元格转为
JSON 格式后的数据。

最容易处理的无疑是第三点,手动将不再需要的变量及时设置为
null,但效果并不明显。

后来,通过操作系统的『活动监视器』(Windows
上是任务管理器)对该工具的每阶段(打开时、导入文件时、筛选时和导出时)进行粗略的内存分析,得到以下报告:

—————- S:报告分割线 —————-
经观察,主要耗内存的是页面渲染进程。下面通过截图说明:
PID 15243 是主进程
PID 15246 是页面渲染进程
PID 15248 是 background 渲染进程

a、首次启动程序时(第 4 行是主进程;第 1 行是页面渲染进程;第 3 行是
background 渲染进程 )

网赌平台哪个信誉好 6

b、导入文件(第 5 行是主进程;第 2 行是页面渲染进程;第 4 行是
background 渲染进程 )
网赌平台哪个信誉好 7

c、筛选数据(第 4 行是主进程;第 1 行是页面渲染进程;第 3 行是
background 渲染进程 )
网赌平台哪个信誉好 8

由于 JavaScript 目前不具有主动回收资源的功能,所以只能主动将对象设置为
null,然后等待 GC 回收。

因此,经过一段时间等待后,内存占用如下:
d、一段时间后(第 4 行是主进程;第 1 行是页面渲染进程;第 3 行是
background 渲染进程 )
网赌平台哪个信誉好 9

由上述可得,页面渲染进程由于页面元素和 Vue 等 UI
相关资源是固定的,占用内存较大且不能回收。主进程占用资源也不能得到很好释放,暂时不知道原因,而
background 渲染进程则较好地释放资源。

—————- E:报告分割线 —————-

根据报告,初步得出的结论是 Vue 和通讯时占用资源较大。

根据该工具的实际应用场景:Excel
数据只在『导入』和『过滤后』两个阶段需要展示,而且展示的是通过
JavaScript 拼接的 HTML 字符串所构成的 DOM 而已。因此将表格数据放置在
Vuex 中,有点滥用资源的嫌疑。

另外,在 background process 中也有存有一份 Excel
数据副本。因此,索性只在 background process 存储一份 Excel
数据,然后每当数据变化时,通过 IPC 让 background process 返回拼接好的
HTML
字符串即可。这样一来,内存占有量立刻下降许多。另外,这也是一个一举多得的优化:

  1. 字符串拼接操作也转移到了
    background process,页面渲染进程进一步减少耗时的操作;
  2. 内存占有量大大减小,响应速度也得到了提升。

其实,这也有点像 Vuex 的『全局单例模式管理』,一份数据就好。

当然,对于 Excel 的基本信息,如行列数、SheetName、标题组等均依然保存在
Vuex。

优化后的内存占有量如下图。与上述报告的第三张图相比(同一阶段),内存占有量下降了
44.419%: 网赌平台哪个信誉好 10
另外,对于不需要响应的数据,可通过 Object.freeze()
冻结起来。这也是一种优化手段。但该工具目前并没有应用到。

至此,优化部分也阐述完毕了!


该工具目前是开源的,欢迎大家使用或推荐给用研组等有需要的人。

你们的反馈(可提交 issues /
pull
request
)能让这个工具在使用和功能上不断完善。

最后,感谢 LV
在产品规划、界面设计和优化上的强力支持。全文完!

打赏支持我写出更多好文章,谢谢!


打赏作者

以发行版本运行

在你完成了你的应用后,你可以按照
应用部署
指导发布一个版本,并且以已经打包好的形式运行应用。

自动更新

如果 Electron
应用没有提供自动更新功能,那么就意味着用户想体验新开发的功能或用上修复
Bug
后的新版本,只能靠用户自己主动地去官网下载,这无疑是糟糕的体验。Electron
提供的 autoUpdater
模块可实现自动更新功能,该模块提供了第三方框架
Squirrel 的接口,但 Electron 目前只内置了
Squirrel.Mac,且它与
Squirrel.Windows(需要额外引入)的处理方式也不一致(在客户端与服务器端两方面)。因此如果对该模块不熟悉,处理起来会相对比较繁琐。具体可以参考笔者的另一篇译文《Electron
自动更新的完整教程(Windows 和
OSX)》

目前 Electron 的 autoUpdater 模块不支持 Linux 系统。

另外,XCel 目前并没有采用 autoUpdater 模块实现自动更新功能,而是利用
Electron 的
DownloadItem
模块实现,而服务器端则采用了 Nuts

Windows

$ .\node_modules\.bin\electron .

把它们想象成这样

Chrome(或其他浏览器)的每个标签页(tab)及其页面,就好比 Electron
中的一个单独渲染进程。即使关闭所有标签页,Chrome 依然存在。这好比
Electron 的主进程,能打开新的窗口或关闭这个应用。

注:在 Chrome
浏览器中,一个标签页(tab)中的页面(即除了浏览器本身部分,如搜索框、工具栏等)就是一个渲染进程。

网赌平台哪个信誉好 11

快速入门

Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs
来创造桌面应用。你可以把它看作一个专注于桌面应用的 Node.js
的变体,而不是 Web 服务器。

这不意味着 Electron 是绑定了 GUI 库的 JavaScript。相反,Electron 使用
web 页面作为它的 GUI,所以你能把它看作成一个被 JavaScript
控制的,精简版的 Chromium 浏览器。

主进程

主进程,通常是一个命名为 main.js 的文件,该文件是每个 Electron
应用的入口。它控制了应用的生命周期(从打开到关闭)。它既能调用原生元素,也能创建新的(多个)渲染进程。另外,Node
API 是内置其中的。

  • 调用原生元素:打开 diglog
    和其它操作系统的交互均是资源密集型操作(注:出于安全考虑,渲染进程是不能直接访问本地资源的),因此都需要在主进程完成。

网赌平台哪个信誉好 12

运行你的应用

一旦你创建了最初的 main.jsindex.htmlpackage.json
这几个文件,你可能会想尝试在本地运行并测试,看看是不是和期望的那样正常运行。

相互通讯

由于主进程和渲染进程各自负责不同的任务,而对于需要协同完成的任务,它们需要相互通讯。IPC就为此而生,它提供了进程间的通讯。但它只能在主进程与渲染进程之间传递信息(即渲染进程之间不能进行直接通讯)。

  • IPC:主进程和渲染进程各自拥有一个 IPC 模块。

网赌平台哪个信誉好 13

macOS / Linux

$ ./node_modules/.bin/electron .

性能优化

下面谈谈『性能优化』,这部分涉及到网赌平台哪个信誉好,运行效率内存占用量
注:以下内容均基于 Excel 样例文件(数据量为:1913 行 x 180
列)得出的结论。

渲染进程

渲染进程是应用的一个浏览器窗口。与主进程不同,它能存在多个(注:一个
Electron
应用只能存在一个主进程)并且相互独立(它也能是隐藏的)。主窗口通常被命名为
index.html。它们就像典型的 HTML 文件,但 Electron 赋予了它们完整的
Node API。因此,这也是它与浏览器的区别。

  • 相互独立:每个渲染进程都是独立的,这意味着某个渲染进程的崩溃,也不会影响其余渲染进程。
  • 隐藏:可隐藏窗口,然后让其在背后运行代码(👍)。

网赌平台哪个信誉好 14

Electron

思路与实现

基于用研组的需求,利用 Electron 和 Vue 的特性对该工具进行开发。

汇成一句话

Electron 应用就像 Node 应用,它也依赖一个 package.json
文件。该文件定义了哪个文件作为主进程,并因此让 Electron
知道从何启动应用。然后主进程能创建渲染进程,并能使用 IPC
让两者间进行消息传递。

网赌平台哪个信誉好 15

至此,Electron
的基础部分介绍完毕。该部分是基于笔者之前翻译的一篇文章《Essential
Electron》
,译文可点击
这里


You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图