计算机教程

必赢娱乐棋牌XCel 项目总结:Electron 与 Vue 的性能优化

3 2月 , 2019  

更贴近原生应用

Electron
的一个缺点是:即使你的应用是一个简单的时钟,但它也不得不包含完整的基础设施(如
Chromium、Node
等)。因此,一般情况下,打包后的程序至少会达到几十兆(根据系统类型进行浮动)。当你的应用越复杂,就越可以忽略文件体积问题。

众所周知,页面的渲染难免会导致『白屏』,而且这里采用了 Vue
这类框架,情况就更加糟糕了。另外,Electron
应用也避免不了『先打开浏览器,再渲染页面』的步骤。下面提供几种方法来减轻这种情况,以让程序更贴近原生应用。

  1. 指定 BrowserWindow 的背景颜色;
  2. 先隐藏窗口,直到页面加载后再显示;
  3. 保存窗口的尺寸和位置,以让程序下次被打开时,依然保留的同样大小和出现在同样的位置上。

对于第一点,若应用的背景不是纯白(#fff)的,那么可指定窗口的背景颜色与其一致,以避免渲染后的突变。

JavaScript

mainWindow = new BrowserWindow({ title: ‘XCel’, backgroundColor:
‘#f5f5f5’, };

1
2
3
4
5
mainWindow = new BrowserWindow({
    title: ‘XCel’,
    backgroundColor: ‘#f5f5f5’,
};
 

对于第二点,由于 Electron
本质是一个浏览器,需要加载非网页部分的资源。因此,我们可以先隐藏窗口。

JavaScript

var mainWindow = new BrowserWindow({ title: ‘ElectronApp’, show: false,
};

1
2
3
4
5
var mainWindow = new BrowserWindow({
    title: ‘ElectronApp’,
    show: false,
};
 

等到渲染进程开始渲染页面的那一刻,在 ready-to-show
的回调函数中显示窗口。

JavaScript

mainWindow.on(‘ready-to-show’, function() { mainWindow.show();
mainWindow.focus(); });

1
2
3
4
5
mainWindow.on(‘ready-to-show’, function() {
    mainWindow.show();
    mainWindow.focus();
});
 

对于第三点,笔者并没有实现,原因如下:

  1. 用户一般是根据当时的情况对程序的尺寸和位置进行调整,即视情况而定。
  2. 以上是我个人臆测,主要是我懒🐶。

其实现方式,可参考《4 must-know tips for building cross platform
Electron
apps》

渲染进程

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

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

性能优化

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

运行你的应用

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

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

必赢娱乐棋牌 1

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

必赢娱乐棋牌 2

主进程

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

多进程!!!

前面说道,JavaScript
天生单线程,即使再快,对于数据量较大时,也会出现拒绝响应的问题。因此需要
Web Worker 或类似的方案去解决。

在这里我不选择 Web worker 的原因有如下几点:

  1. 有其它更好的替代方案:一个主进程能创建多个渲染进程,通过 IPC
    即可进行数据交互;
  2. Electron 不支持 Web
    Worker!(当然,可能会在新版本支持,最新信息请关注官方)

Electron 作者在 2014.11.7 在《state of web worker support?》 issue
中回复了以下这一段:

Node integration doesn’t work in web workers, and there is no plan to
do. Workers in Chromium are implemented by starting a new thread, and
Node is not thread safe. Back in past we had tried to add node
integration to web workers in Atom, but it crashed too easily so we
gave up on it.

因此,我们最终采用了创建一个新的渲染进程 background process
进行处理数据。由 Electron 章节可知,每个 Electron
渲染进程是独立的,因此它们不会互相影响。但这也带来了一个问题:它们不能相互通讯?

错!下面有 3 种方式进行通讯:

  1. Storage
    API
    :对某个标签页的
    localStorage/sessionStorage 对象进行增删改时,其他标签页能通过
    window.storage 事件监听到。
  2. IndexedDB:IndexedDB
    是一个为了能够在客户端存储可观数量的结构化数据,并且在这些数据上使用索引进行高性能检索的
    API。
  3. 通过主进程作为中转站:设主界面的渲染进程是 A,background process
    是 B,那么 A 先将 Excel 数据传递到主进程,然后主进程再转发到 B。B
    处理完后再原路返回,具体如下图。当然,也可以将数据存储在主进程中,然后在多个渲染进程中使用
    remote 模块来访问它。

该工具采用了第三种方式的第一种情况:
必赢娱乐棋牌 3

1、主页面渲染进程 A 的代码如下:

JavaScript

//① ipcRenderer.send(‘filter-start’, { filterTagList:
this.filterTagList, filterWay: this.filterWay, curActiveSheetName:
this.activeSheet.name }) // ⑥ 在某处接收 filter-response 事件
ipcRenderer.on(“filter-response”, (arg) => { // 得到处理数据 })

1
2
3
4
5
6
7
8
9
10
11
12
//①
ipcRenderer.send(‘filter-start’, {
    filterTagList: this.filterTagList,
    filterWay: this.filterWay,
    curActiveSheetName: this.activeSheet.name
})
 
// ⑥ 在某处接收 filter-response 事件
ipcRenderer.on("filter-response", (arg) => {
    // 得到处理数据
})
 

2、作为中转站的主进程的代码如下:

JavaScript

//② ipcMain.on(“filter-start”, (event, arg) => { // webContents
用于渲染和控制 web page
backgroundWindow.webContents.send(“filter-start”, arg) }) // ⑤
用于接收返回事件 ipcMain.on(“filter-response”, (event, arg) => {
mainWindow.webContents.send(“filter-response”, arg) })

1
2
3
4
5
6
7
8
9
10
11
//②
ipcMain.on("filter-start", (event, arg) => {
    // webContents 用于渲染和控制 web page
    backgroundWindow.webContents.send("filter-start", arg)
})
 
// ⑤ 用于接收返回事件
ipcMain.on("filter-response", (event, arg) => {
    mainWindow.webContents.send("filter-response", arg)
})
 

3、处理繁重数据的 background process 渲染进程 B 的代码如下:

JavaScript

// ③ ipcRenderer.on(‘filter-start’, (event, arg) => { // 进行运算 …
// ④ 运算完毕后,再通过 IPC 原路返回。主进程和渲染进程 A
也要建立相应的监听事件 ipcRenderer.send(‘filter-response’, { filRow:
tempFilRow }) })

1
2
3
4
5
6
7
8
9
10
11
// ③
ipcRenderer.on(‘filter-start’, (event, arg) => {
    // 进行运算
    …
 
    // ④ 运算完毕后,再通过 IPC 原路返回。主进程和渲染进程 A 也要建立相应的监听事件
    ipcRenderer.send(‘filter-response’, {
        filRow: tempFilRow
    })
})
 

至此,我们将『读取文件』、『过滤数据』和『导出文件』三大耗时的数据操作均转移到了
background process 中处理。

这里,我们只创建了一个
background process,如果想要做得更极致,我们可以新建『CPU 线程数- 1 』
个的 background process
同时对数据进行处理,然后在主进程对处理后数据进行拼接,最后再将拼接后的数据返回到主页面的渲染进程。这样就可以充分榨干
CPU 了。当然,在此笔者不会进行这个优化。

不要为了优化而优化,否则得不偿失。 —— 某网友

打造你第一个 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>

汇成一句话

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

必赢娱乐棋牌 4

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


macOS / Linux

$ ./node_modules/.bin/electron .

相关技术

如果对某项技术比较熟悉,则可略读/跳过。

Linux

$ ./electron/electron your-app/

项目背景

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

快速入门

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

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

js-xlsx

该库支持各种电子表格格式的解析与生成。它由 JavaScript 实现,适用于前端和
Node。详情>>

目前支持读入的格式有(不断更新):

  • Excel 2007+ XML Formats (XLSX/XLSM)
  • Excel 2007+ Binary Format (XLSB)
  • Excel 2003-2004 XML Format (XML “SpreadsheetML”)
  • Excel 97-2004 (XLS BIFF8)
  • Excel 5.0/95 (XLS BIFF5)
  • OpenDocument Spreadsheet (ODS)

支持写出的格式有:

  • XLSX
  • CSV (and general DSV)
  • JSON and JS objects (various styles)

目前该库提供的 sheet_to_json 方法能将读入的 Excel 数据转为 JSON
格式。而对于导出操作,我们需要为 js-xlsx 提供指定的 JSON 格式。

更多关于 Excel 在 JavaScript
中处理的知识可查看凹凸实验室的《Node读写Excel文件探究实践》。但该文章存在两处问题(均在
js-xlsx 实战的导出表格部分):

  1. 生成头部时,Excel 的列信息简单地通过 String.fromCharCode(65+j)
    生成。当列大于 26 时会出现问题。这个问题会在后面章节中给出解决方案;
  2. 转换成 worksheet
    需要的结构处,出现逻辑性错误,并且会导致严重的性能问题。逻辑问题在此不讲述,我们看看性能问题:
    随着 ECMAScript 的不断更新,JavaScript
    变得更加强大和易用。尽管如此,我们还是要做到『物尽所用』,而不要『大材小用』,否则可能会得到“反效果”。这里导致性能问题的正是
    Object.assign()
    方法,该方法可以把任意多个源对象的可枚举属性拷贝至目标对象,并返回目标对象。由于该方法自身的实现机制,会在此案例中产生大量的冗余操作。在该案例中,单元格信息是唯一的,所以直接通过
    forEach 为一个空对象赋值即可。提升 N
    倍性能的同时,也把逻辑性错误解决了。

原来的:

JavaScript

var result = 某数组.reduce((prev, next) => Object.assign({}, prev,
{[next.position]: {v: next.v}}), {});

1
2
var result = 某数组.reduce((prev, next) =&gt; Object.assign({}, prev, {[next.position]: {v: next.v}}), {});
 

改为:

JavaScript

var result = 某数组.forEach((v, i) => data[v.position]= {v: v.v})

1
2
var result = 某数组.forEach((v, i) =&gt; data[v.position]= {v: v.v})
 

实践是检验真理的唯一标准

在理解上述知识后,下面就谈谈在该项目实践中总结出来的技巧、难点和重点

Windows

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

实现思路

  1. 通过 js-xlsx 将 Excel 文件解析为 JSON 数据
  2. 根据筛选条件对 JSON 数据进行筛选过滤
  3. 将过滤后的 JSON 数据转换成 js-xlsx 指定的数据结构
  4. 利用 js-xlsx 对转换后的数据生成 Excel 文件

纸上得来终觉浅,绝知此事要躬行

以发行版本运行

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

强大的 GPU 加速

将拼接的字符串插入 DOM
后,出现了另外一个问题:滚动会很卡。猜想这是渲染问题,毕竟 34
万个单元格同时存在于界面中。

添加 transform: translate3d(0, 0, 0) / translateZ(0) 属性启动 GPU
渲染,即可解决这个渲染性能问题。再次感叹该属性的强大。🐂

后来,考虑到用户并不需要查看全部数据,只需展示部分数据让用户进行参考即可。我们对此只渲染前
30/50 行数据。这样即可提升用户体验,也能进一步优化性能。

主进程与渲染进程的区别

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

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

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

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

CSS、JavaScript 和 Electron 相关的知识和技巧

参照下面例子

复制并且运行这个库
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

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

2017/03/01 · 基础技术 ·
Javascript,
算法

本文作者: 伯乐在线
刘健超-J.c
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者

XCEL 是由京东用户体验设计部凹凸实验室推出的一个 Excel
数据清洗工具,其通过可视化的方式让用户轻松地对 Excel 数据进行筛选。

XCEL 基于 Electron 和 Vue 2.x,它不仅跨平台(windows 7+、Mac 和
Linux),而且充分利用 Electron 多进程任务处理等功能,使其性能优异。

落地页:https://xcel.aotu.io/ ✨✨✨
项目地址:https://github.com/o2team/xcel ✨✨✨

手工下载 Electron 二进制文件

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

为什么选择 Vue

对于笔者来说:

  • 简单易用,一般使用只需看官方文档。
  • 数据驱动视图,所以基本不用操作 DOM 了。
  • 框架的存在是为了帮助我们应对复杂度。
  • 全家桶的好处是:对于一般场景,我们就不需要考虑用哪些个库(插件)。

Vue 1.x -> Vue 2.0 的版本迁移用
vue-migration-helper
即可分析出大部分需要更改的地方。

网上已有很多关于 Vue 的教程,故在此不再赘述。至此,Vue 部分介绍完毕。


electron-prebuilt

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

electron .

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

两个进程(重点)

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

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

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

Windows

$ .\node_modules\.bin\electron .

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

任选一种支付方式

必赢娱乐棋牌 5
必赢娱乐棋牌 6

1 赞 2 收藏
评论

macOS

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

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

为什么它如此重要?

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

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

Vue 性能真的好?

Vue 一直标榜着自己性能优异,但当数据量上升到一定量级时(如 1913 x 180 ≈
34 万个数据单元),会出现严重的性能问题(未做相应优化的前提下)。

如直接通过列表渲染 v-for 渲染数据时,会导致程序卡死。
答:通过查阅相关资料可得, v-for
在初次渲染时,需要对每个子项进行初始化(如数据绑定等操作,以便拥有更快的更新速度),这对于数据量较大时,无疑会造成严重的性能问题。

当时,我想到了两种解决思路:

  1. Vue 是数据驱动视图的,对数据分段 push,即将一个庞大的任务分割为 N
    份。
  2. 自己拼接 HTML 字符串,再通过 innerHTML 一次性插入。

最终,我选择了第二条,理由是:

  1. 性能最佳,因为每次执行数据过滤时,Vue 都要进行 diff,性能不佳。
  2. 更符合当前应用的需求:纯展示且无需动画过渡等。
  3. 实现更简单

将原本繁重的 DOM 操作(Vue)转换为 JavaScript
的拼接字符串后,性能得到了很大提升(不会导致程序卡死而渲染不出视图)。这种优化方式难道不就是
Vue、React
等框架解决的问题之一吗?只不过框架考虑的场景更广,有些地方需要我们自己根据实际情况进行优化而已。

在浏览器当中,JavaScript 的运算在现代的引擎中非常快,但 DOM
本身是非常缓慢的东西。当你调用原生 DOM API 的时候,浏览器需要在
JavaScript 引擎的语境下去接触原生的 DOM
的实现,这个过程有相当的性能损耗。所以,本质的考量是,要把耗费时间的操作尽量放在纯粹的计算中去做,保证最后计算出来的需要实际接触真实
DOM 的操作是最少的。 —— 《Vue
2.0——渐进式前端解决方案》

当然,由于 JavaScript
天生单线程,即使执行数速度再快,也难免会导致页面有短暂的时间拒绝用户的输入。此时可通过
Web Worker 或其它方式解决,这也将是我们后续讲到的问题。

也有网友提供了优化大量列表的方法:https://clusterize.js.org/。但在此案例中笔者并没有采用此方式。

特性一览

  • 基于 Electron 研发并打包成为原生应用,用户体验良好;
  • 可视化操作 Excel 数据,支持文件的导入导出;
  • 拥有单列运算逻辑、多列运算逻辑和双列范围逻辑三种筛选方式,并且可通过“且”、“或”和“编组”的方式任意组合。

它由什么组成?

Electron 结合了 ChromiumNode.js 和用于调用操作系统本地功能的
API(如打开文件窗口、通知、图标等)。

  • Chromium:Google 创造的一个开源库,并用于 Google 的浏览器
    Chrome。
  • Node.js(Node):一个在服务器运行 JavaScript
    的运行时(runtime),它拥有访问文件系统和网络权限(你的电脑也可以是一台服务器!)。

必赢娱乐棋牌 7

主进程

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

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

必赢娱乐棋牌 8


相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图