摘要:而這里的單元格信息是唯一的,所以直接通過為一個(gè)空對(duì)象賦值即可。和相關(guān)的知識(shí)和技巧高亮的列單元格采用展示。在中,被選中的單元格會(huì)高亮相應(yīng)的行和列,以提醒用戶。
XCEL 是一個(gè) Excel 數(shù)據(jù)清洗工具,其通過可視化的方式讓用戶輕松地對(duì) Excel 數(shù)據(jù)進(jìn)行篩選。
XCEL 基于 Electron 和 Vue 2.0 進(jìn)行開發(fā),充分利用 Electron 多進(jìn)程任務(wù)處理等功能,使其擁有高性能、跨平臺(tái)(windows 7+、Mac 和 Linux)的特性。
落地頁:https://xcel.aotu.io/ ???
項(xiàng)目地址:https://github.com/o2team/xcel ???
用戶研究的定量研究和輕量級(jí)數(shù)據(jù)處理中,均需對(duì)數(shù)據(jù)進(jìn)行清洗處理,用以剔除異常數(shù)據(jù),保證數(shù)據(jù)結(jié)果的信度和效度。目前因調(diào)研數(shù)據(jù)和輕量級(jí)數(shù)據(jù)的多變性,對(duì)輕量級(jí)數(shù)據(jù)清洗往往采取人工清洗,缺少統(tǒng)一、標(biāo)準(zhǔn)的清洗流程,但對(duì)于調(diào)研和輕量級(jí)的數(shù)據(jù)往往是需要保證數(shù)據(jù)穩(wěn)定性的,因此,在對(duì)數(shù)據(jù)進(jìn)行清洗的時(shí)候最好有可以標(biāo)準(zhǔn)化的清洗方式。
特性一覽基于 Electron 研發(fā)并打包成為原生應(yīng)用,用戶體驗(yàn)良好;
可視化操作 Excel 數(shù)據(jù),支持文件的導(dǎo)入導(dǎo)出;
擁有單列運(yùn)算邏輯、多列運(yùn)算邏輯和雙列范圍邏輯三種篩選方式,并且可通過“且”、“或”和“編組”的方式任意組合。
思路與實(shí)現(xiàn)結(jié)合用研組的需求,我們利用 Electron 和 Vue 的特性對(duì)該工具進(jìn)行開發(fā)。
技術(shù)選型Electron:桌面端跨平臺(tái)框架,為 Web 提供了原生接口的權(quán)限。打包后的程序兼容 Windows 7 及以上、Mac、Linux 的 32 / 64 位系統(tǒng)。詳情>>
Vue 全家桶:Vue 擁有數(shù)據(jù)驅(qū)動(dòng)視圖的特性,適合重?cái)?shù)據(jù)交互的應(yīng)用。詳情>>
js-xlsx:各種電子表格格式的解析器和生成器。純 JavaScript 實(shí)現(xiàn),適用于 Node.js 和 Web 前端。詳情>>
實(shí)現(xiàn)思路通過 js-xlsx 解析 Excel 文件生成 JSON 格式
根據(jù)篩選條件對(duì) JSON 數(shù)據(jù)進(jìn)行篩選過濾
將過濾后的 JSON 數(shù)據(jù)生成 js-xlsx 指定的數(shù)據(jù)結(jié)構(gòu)
利用 js-xlsx 對(duì)轉(zhuǎn)換后的數(shù)據(jù)生成 Excel 文件
紙上得來終覺淺,絕知此事要躬行
相關(guān)技術(shù)如果對(duì)某項(xiàng)技術(shù)比較熟悉可略讀/跳過。
Electron Electron 是什么?Electron 是一個(gè)能讓你通過 JavaScript、HTML 和 CSS 構(gòu)建桌面應(yīng)用的框架。這些應(yīng)用能打包到 Mac、Windows 和 Linux 電腦上運(yùn)行,當(dāng)然它們也能上架到 Mac 和 Windows 的 app stores。
JavaScript、HTML 和 CSS 都是 Web 語言,這就意味著它們都是組成網(wǎng)站的一部分,瀏覽器(如 Chrome)能將這些代碼轉(zhuǎn)為可視化圖像。
Electron 是一個(gè)框架:Electron 對(duì)底層代碼進(jìn)行抽象和封裝,讓開發(fā)者能在此之上構(gòu)建項(xiàng)目。
為什么它如此重要?通常來說,桌面應(yīng)用都需要用每個(gè)操作系統(tǒng)對(duì)應(yīng)的原生語言進(jìn)行開發(fā)。這意味著需要擁有 3 個(gè)團(tuán)隊(duì)為這個(gè)應(yīng)用編寫 3 個(gè)相應(yīng)的版本。Electron 則允許你通過 web 語言編寫一次即可。
原生(操作系統(tǒng))語言:用于開發(fā)主流操作系統(tǒng)的應(yīng)用的原生語言如下(大多數(shù)情況下):Mac 對(duì)應(yīng) Objective C、Linux 對(duì)應(yīng) C、Windows 對(duì)應(yīng) C++。
它由什么組成?Electron 結(jié)合了 Chromium、Node.js 和用于調(diào)用操作系統(tǒng)本地功能的 API(如打開文件窗口、通知、圖標(biāo)等)。
Chromium:Google 創(chuàng)造的一個(gè)開源庫,并用于 Google 的瀏覽器 Chrome。
Node.js(Node):一個(gè)用于在服務(wù)器運(yùn)行 JavaScript 的運(yùn)行時(shí)(runtime),它擁有文件系統(tǒng)和網(wǎng)絡(luò)的權(quán)限(你的電腦也可以是一臺(tái)服務(wù)器?。?/p>
開發(fā)體驗(yàn)如何?基于 Electron 的開發(fā),就好像開發(fā)一個(gè)網(wǎng)頁一樣,而且能夠無縫地 使用 Node?;蛘哒f:就好像構(gòu)建一個(gè) Node app,并通過 HTML 和 CSS 構(gòu)建界面。另外,你只需為一個(gè)瀏覽器(最新的 Chrome)進(jìn)行設(shè)計(jì)(即無需考慮兼容性)。
使用內(nèi)置的 Node:這還不是全部!除了 Node API,你還可以使用托管在 npm 上,超過 350,000 個(gè)的模塊。
一個(gè)瀏覽器:并非所有瀏覽器都提供一致的樣式,因此 web 設(shè)計(jì)師和開發(fā)者時(shí)常不得不花費(fèi)更多的精力去讓一個(gè)網(wǎng)站在不同的瀏覽器上看起來一致。
最新的 Chrome:可使用超過 90% 的 ES2015 特性和其它很酷的特性(如 CSS 變量)。
兩個(gè)進(jìn)程(重點(diǎn))Electron 有兩個(gè)種進(jìn)程:『主進(jìn)程』和『渲染進(jìn)程』。有些模塊只能工作在其中一個(gè)進(jìn)程上,而有些則能工作在兩個(gè)進(jìn)程上。主進(jìn)程更多地充當(dāng)幕后角色,而渲染進(jìn)程則是應(yīng)用的每個(gè)窗口。
PS:可通過任務(wù)管理器(PC)/活動(dòng)監(jiān)視器(Mac)查看進(jìn)程的相關(guān)信息。
模塊:Electron 的 API 是根據(jù)它們的功能進(jìn)行分組。例如:dialog 模塊擁有所有原生 dialog 的 API,如打開文件、保存文件和彈窗。
主進(jìn)程主進(jìn)程,通常是一個(gè)命名為 main.js 的文件,該文件是每個(gè) Electron 應(yīng)用的入口。它控制了應(yīng)用的生命周期(從打開到關(guān)閉)。它能調(diào)用原生元素和創(chuàng)建新的(多個(gè))渲染進(jìn)程,而且整個(gè) Node API 是內(nèi)置其中的。
調(diào)用原生元素:打開 diglog 和其它操作系統(tǒng)交互均是資源密集型操作(注:出于安全考慮,渲染進(jìn)程是不能直接調(diào)用本地資源的),因此都需要在主進(jìn)程完成。
渲染進(jìn)程渲染進(jìn)程是應(yīng)用的一個(gè)瀏覽器窗口。與主進(jìn)程不同,它能存在多個(gè)(注:一個(gè) Electron 應(yīng)用只能有一個(gè)主進(jìn)程)并且是相互獨(dú)立的。它們也能是隱藏的。它通常被命名為 index.html。它們就像典型的 HTML 文件,但在 Electron 中,它們能獲取完整的 Node API 特性。因此,這也是它與其它瀏覽器不同的地方。
相互獨(dú)立:每個(gè)渲染進(jìn)程都是獨(dú)立的,這意味著就算它們某個(gè)崩潰了,也不會(huì)影響其余的渲染進(jìn)程。
隱藏的:你可以設(shè)置一個(gè)窗口是隱藏的,然后讓它只在背后執(zhí)行代碼(?)。
把它們想象成這樣在 Chrome(或其它瀏覽器)中的每個(gè)標(biāo)簽頁(tab) 和其內(nèi)的頁面,就好比 Electron 中的一個(gè)多帶帶渲染進(jìn)程。如果你關(guān)閉所有標(biāo)簽頁,Chrome 依然存在,這好比 Electron 的主進(jìn)程,而且你能打開一個(gè)新的窗口或關(guān)閉這個(gè)應(yīng)用。
相互通訊注:一般情況下,在 Chrome 瀏覽器中,一個(gè)標(biāo)簽頁(tab)中的頁面(即除了瀏覽器本身部分,如搜索框、工具欄等)就是一個(gè)渲染進(jìn)程。
盡管主進(jìn)程和渲染進(jìn)程都有各自的任務(wù),但它們之間也有需要協(xié)同完成的任務(wù)。因此它們之間需要通訊。IPC就為此而生,它提供了進(jìn)程間的通訊。但它只能在主進(jìn)程與渲染進(jìn)程之間傳遞信息。
IPC:主進(jìn)程和渲染進(jìn)程都有一個(gè) IPC 模塊。
匯成一句話Electron 應(yīng)用就像 Node 應(yīng)用,它也依賴一個(gè) package.json 文件。該文件定義了哪個(gè)文件作為主進(jìn)程,并因此讓 Electron 知道從何啟動(dòng)你的應(yīng)用。然后主進(jìn)程能創(chuàng)建渲染進(jìn)程,并能使用 IPC 讓兩者間進(jìn)行消息傳遞。
至此,Electron 的基礎(chǔ)部分介紹完畢。該部分是基于我之前翻譯的一篇文章《Essential Electron》,譯文可點(diǎn)擊 這里。
-----
Vue 全家桶目前,該工具應(yīng)用了 Vue、Vuex、Vuex-router。在工具基本定型階段,由 1.x 升級(jí)到了 2.0 (Vuex 暫未升級(jí))。
為什么選擇 Vue對(duì)于我來說:
簡(jiǎn)單易用,一般使用只需看官方文檔。
數(shù)據(jù)驅(qū)動(dòng)視圖,所以基本不用操作 DOM 了。
框架的存在是為了幫助我們應(yīng)對(duì)復(fù)雜度。
全家桶的好處是:對(duì)于一般場(chǎng)景,我就不需要考慮用哪些個(gè)庫(插件)。
Vue 1.x -> Vue 2.0 的版本遷移用 vue-migration-helper 即可分析出大部分需要更改的地方。
網(wǎng)上已經(jīng)有很多關(guān)于 Vue 的信息了。至此,Vue 部分介紹完畢。
js-xlsx該庫支持各種電子表格格式的解析和生成。它由純 JavaScript 實(shí)現(xiàn),適用于前端和 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)
只要能提供讀(解析)和寫,剩下的就是靠 JavaScript 處理解析出來的數(shù)據(jù)(JSON)了。目前該庫提供了 sheet_to_json 方法,該方法能將讀入的 Excel 數(shù)據(jù)轉(zhuǎn)為 JSON 格式。由于導(dǎo)出時(shí)需要提供特定的 JSON 格式,因此這部分需要我們自己實(shí)現(xiàn)。
更多關(guān)于 Excel 在 JavaScript 中處理的知識(shí)可關(guān)注:凹凸實(shí)驗(yàn)室的《Node讀寫Excel文件探究實(shí)踐》。但該文章存在兩處問題(均在 js-xlsx 實(shí)戰(zhàn)的導(dǎo)出表格部分):
生成頭部時(shí),Excel 的列信息簡(jiǎn)單地通過 String.fromCharCode(65+j) 生成,但列大于 26 時(shí)就會(huì)出現(xiàn)問題。這個(gè)問題會(huì)在后面章節(jié)中給出解決方案;
轉(zhuǎn)換成 worksheet 需要的結(jié)構(gòu)處,出現(xiàn)邏輯性錯(cuò)誤,并且會(huì)導(dǎo)致嚴(yán)重的性能問題。邏輯問題在此不講述,我們講下性能問題:
ECMAScript 的不斷更新,讓 JavaScript 更加強(qiáng)大和易用。盡管如此,我們還是要做到『物盡所用』,而不要『大材小用』,否則會(huì)得到反效果。這里導(dǎo)致性能問題的正是 Object.assign() 方法,該方法可以把任意多個(gè)的源對(duì)象自身的可枚舉屬性拷貝給目標(biāo)對(duì)象,然后返回目標(biāo)對(duì)象。由于該方法自身的實(shí)現(xiàn)機(jī)制,會(huì)在這里產(chǎn)生大量的冗余操作。而這里的單元格信息是唯一的,所以直接通過 forEach 為一個(gè)空對(duì)象賦值即可。提升 N 倍性能的同時(shí),也把邏輯性錯(cuò)誤解決了。
原來的:
var result = 某數(shù)組.reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});
改為:
var result = 某數(shù)組.forEach((v, i) => data[v.position]= {v: v.v})
實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)
在理解上述知識(shí)的前提下,下面就談?wù)勔恍┰趯?shí)踐中總結(jié)出來的技巧、難點(diǎn)和重點(diǎn)。
Excel 單元格采用 table 展示。在 Excel 中,被選中的單元格會(huì)高亮相應(yīng)的『行』和『列』,以提醒用戶。在該應(yīng)用中也有做相應(yīng)處理,橫向高亮采用 tr:hover 實(shí)現(xiàn),而縱向呢?這里所采用的一個(gè)技巧是:
假設(shè) HTML 結(jié)構(gòu)如下:
div.container table tr td
CSS 代碼如下:
.container { overflow:hidden; } td { position: relative; } td:hover::after { position: absolute; left: 0; right: 0; top: -1個(gè)億px; // 小目標(biāo)達(dá)成,不過是負(fù)的? bottom: -1個(gè)億px; z-index: -1; // 避免遮住自身和同列 td 的內(nèi)容、border 等 }斜分割線
如圖:
分割線可以通過 ::after/::before 偽類元素實(shí)現(xiàn)一條直線,然后通過 transform:rotate(); 旋轉(zhuǎn)特定角度實(shí)現(xiàn)。但這種實(shí)現(xiàn)的一個(gè)問題是:由于寬度是不定的,因此需要通過 JavaScript 運(yùn)算才能得到準(zhǔn)確的對(duì)角分割線。
因此,這里可以通過 CSS 線性漸變 linear-gradient(to top right, transparent, transparent calc(50% - .5px), #d3d6db calc(50% - .5px), #d3d6db calc(50% + .5px), transparent calc(50% + .5px)) 實(shí)現(xiàn)。無論寬高如何變,依然妥妥地自適應(yīng)。
Excel 的列轉(zhuǎn)換Excel 的列需要用『字母』表示,但不能簡(jiǎn)單地通過 String.fromCharCode() 實(shí)現(xiàn),因?yàn)楫?dāng)超出 26列 時(shí)會(huì)產(chǎn)生問題(如:第 27 列,String.fromCharCode(65+26) 得到的是 [,而不是 AA)。因此,這需要通過『十進(jìn)制和26進(jìn)制轉(zhuǎn)換』算法來實(shí)現(xiàn)。
// 將指定的自然數(shù)轉(zhuǎn)換為26進(jìn)制表示。映射關(guān)系:[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 }
// 將指定的26進(jìn)制轉(zhuǎn)換為自然數(shù)。映射關(guān)系:[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 }為 DOM 的 File 對(duì)象增加了 path 屬性
Electron 為 File 對(duì)象額外增了 path 屬性,該屬性可得到文件在文件系統(tǒng)上的真實(shí)路徑。因此,你可以利用 Node 為所欲為?。應(yīng)用場(chǎng)景有:拖拽文件后,通過 Node 提供的 File API 讀取文件等。
支持常見的編輯功能,如粘貼和復(fù)制Electron 應(yīng)用在 MacOS 中默認(rèn)不支持『復(fù)制』『粘貼』等常見編輯功能,因此需要為 MacOS 顯式地設(shè)置復(fù)制粘貼等編輯功能的菜單欄,并為此設(shè)置相應(yīng)的快捷鍵。
// darwin 就是 MacOS if (process.platform === "darwin") { var template = [{ label: "FromScratch", submenu: [{ label: "Quit", accelerator: "CmdOrCtrl+Q", click: function() { app.quit(); } }] }, { label: "Edit", submenu: [{ label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" }, { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" }, { type: "separator" }, { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" }, { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" }, { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" }, { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }] }]; var osxMenu = menu.buildFromTemplate(template); menu.setApplicationMenu(osxMenu); }更貼近原生應(yīng)用
Electron 的一個(gè)缺點(diǎn)是:即使你的應(yīng)用是一個(gè)簡(jiǎn)單的時(shí)鐘,但它也不得不包含完整的基礎(chǔ)設(shè)施(如 Chromium、Node 等)。因此,一般情況,打包后的程序至少會(huì)達(dá)到幾十兆(根據(jù)系統(tǒng)類型進(jìn)行浮動(dòng))。當(dāng)你的應(yīng)用越復(fù)雜,就越可以忽略這部分了。
眾所周知,頁面的渲染難免會(huì)導(dǎo)致『白屏』,而且這里采用了 Vue 框架,情況就更加糟糕了。另外,Electron 應(yīng)用也避免不了『先打開瀏覽器,再渲染頁面』的步驟。下面提供幾種方法來減輕這種情況,以讓程序更貼近原生應(yīng)用。
指定 BrowserWindow 的背景顏色;
先隱藏窗口,直到頁面加載后再顯示;
保存窗口的尺寸和位置,以讓程序下次被打開時(shí),依然保留的同樣大小和出現(xiàn)在同樣的位置上。
對(duì)于第一點(diǎn),若程序的背景不是純白(#fff)的,那么可指定窗口的背景顏色與其一致,以避免突變。
mainWindow = new BrowserWindow({ title: "XCel", backgroundColor: "#f5f5f5", };
對(duì)于第二點(diǎn),由于 Electron 本質(zhì)是一個(gè)瀏覽器,需要加載非網(wǎng)頁部分的資源。因此,我們可以先隱藏窗口。
var mainWindow = new BrowserWindow({ title: "ElectronApp", show: false, };
等到渲染進(jìn)程開始渲染頁面的那一刻,在 ready-to-show 的回調(diào)函數(shù)中顯示窗口。
mainWindow.on("ready-to-show", function() { mainWindow.show(); mainWindow.focus(); });
對(duì)于第三點(diǎn),我并沒有實(shí)現(xiàn),原因如下:
用戶一般是根據(jù)當(dāng)時(shí)的情況對(duì)程序的尺寸和位置進(jìn)行調(diào)整,即視情況而定。
以上是我個(gè)人臆測(cè),主要是我懶?。
其實(shí)現(xiàn)方式,可參考《4 must-know tips for building cross platform Electron apps》。
如何在渲染進(jìn)程調(diào)用原生彈框?在渲染進(jìn)程中調(diào)用原本專屬于主進(jìn)程中的 API (如彈框)的方式有兩種:
IPC 通訊模塊:先在主進(jìn)程通過 ipcMain 進(jìn)行監(jiān)聽,然后在渲染進(jìn)程通過 ipcRenderer 進(jìn)行觸發(fā);
remote 模塊:該模塊提供了一種在渲染進(jìn)程(網(wǎng)頁)和主進(jìn)程之間進(jìn)行進(jìn)程間通訊(IPC)的簡(jiǎn)便途徑。
對(duì)于第一種,有需要就在評(píng)論區(qū)留言;
對(duì)于第二種, 在渲染進(jìn)程中,運(yùn)行以下代碼即可:
const remote = require("electron").remote remote.dialog.showMessageBox({ type: "question", buttons: ["不告訴你", "沒有夢(mèng)想"], defaultId: 0, title: "XCel", message: "你的夢(mèng)想是什么?" }自動(dòng)更新
如果 Electron 應(yīng)用沒有了自動(dòng)更新的功能,那么意味著用戶想體驗(yàn)?zāi)阈麻_發(fā)的功能或用上修復(fù) Bug 后的新版本,只能靠自己主動(dòng)地去官網(wǎng)下載,這無疑是糟糕的體驗(yàn)。Electron 提供的 autoUpdater 模塊可實(shí)現(xiàn)自動(dòng)更新功能,該模塊提供了第三方框架 Squirrel 的接口,但 Electron 目前只內(nèi)置了 Squirrel.Mac,且它與 Squirrel.Windows(需要額外引入)的處理方式也不一致(在客戶端與服務(wù)器端兩方面),因此如果剛接觸該模塊,會(huì)發(fā)現(xiàn)處理起來相對(duì)比較繁瑣。具體可以參考我的一篇譯文《Electron 自動(dòng)更新的完整教程(Windows 和 OSX)》。
目前 Electron 的 autoUpdater 模塊不支持 Linux 系統(tǒng)。
另外,XCel 目前并沒有采用 autoUpdater 模塊實(shí)現(xiàn)自動(dòng)更新功能,而是利用 Electron 的 DownloadItem 模塊實(shí)現(xiàn)。而服務(wù)器端則采用 Nuts。
為 Electron 應(yīng)用生成 Windows 安裝包通過 electron-builder 即可直接生成常見的 MacOS 安裝包,但它生成的 Windows 的安裝包卻略顯簡(jiǎn)潔。
Mac 常見的安裝模式,將“左側(cè)的應(yīng)用圖標(biāo)”拖拽到“右側(cè)的 Applications”即可
通過 electron-builder 生成的 Windows 安裝包與我們?cè)?Windows 上常見的軟件安裝界面不太一樣,它沒有安裝向?qū)Ш忘c(diǎn)擊“下一步”的按鈕,只有一個(gè)安裝時(shí)的 gif 動(dòng)畫(默認(rèn)的 gif 動(dòng)畫如下圖,當(dāng)然你也可以指定特定的 gif 動(dòng)畫),因此也就沒有讓用戶選擇安裝路徑等權(quán)利。
Windows 安裝時(shí) 默認(rèn)顯示的 gif 動(dòng)畫
如果你想為打包后的 Electron 應(yīng)用(即通過 electron-packager/electron-builder 生成的 、可直接運(yùn)行的程序目錄)生成需要點(diǎn)擊“下一步”和可讓用戶指定安裝路徑的常見安裝包,可以通過 NSIS 程序,具體可看這篇教程 《[教學(xué)]只要10分鐘學(xué)會(huì)使用 NSIS 包裝您的桌面軟體–安裝程式打包。完全免費(fèi)?!?。
NSIS(Nullsoft Scriptable Install System)是一個(gè)開源的 Windows 系統(tǒng)下安裝程序制作程序。它提供了安裝、卸載、系統(tǒng)設(shè)置、文件解壓縮等功能。這如其名字所指出的那樣,NSIS 是通過它的腳本語言來描述安裝程序的行為和邏輯的。NSIS 的腳本語言和通常的編程語言有類似的結(jié)構(gòu)和語法,但它是為安裝程序這類應(yīng)用所設(shè)計(jì)的。
至此,CSS、JavaScript 和 Electron 相關(guān)的知識(shí)和技巧 部分闡述完畢。
性能優(yōu)化下面談?wù)劇盒阅軆?yōu)化』,這部分涉及到運(yùn)行效率和內(nèi)存占用量。
注:以下內(nèi)容均基于 Excel 樣例文件(數(shù)據(jù)量為:1913 行 x 180 列)得出的結(jié)論。
Vue 一直標(biāo)榜著自己性能優(yōu)異,但當(dāng)數(shù)據(jù)量上升到一定量級(jí)時(shí)(如 1913 x 180 ≈ 34 萬個(gè)數(shù)據(jù)單元),會(huì)出現(xiàn)嚴(yán)重的性能問題(不做相應(yīng)優(yōu)化的前提下)。
如直接通過列表渲染 v-for 渲染數(shù)據(jù)時(shí),會(huì)導(dǎo)致程序卡死。
答:通過查閱相關(guān)資料可得(猜測(cè)), v-for 是通過一條條數(shù)據(jù)在構(gòu)建后插入 DOM 的,這對(duì)于數(shù)據(jù)量較大時(shí),無疑會(huì)造成嚴(yán)重的性能問題。
當(dāng)時(shí),我想到了兩種解決思路:
Vue 是數(shù)據(jù)驅(qū)動(dòng)視圖的,對(duì)數(shù)據(jù)分段 push,即將一個(gè)龐大的任務(wù)分割為 N 份。
自己拼接 HTML 字符串,再通過 innerHTML 一次性插入。
最終,我選擇了第二條,理由是:
性能最佳,因?yàn)槊看螆?zhí)行數(shù)據(jù)過濾時(shí),Vue 都要進(jìn)行 diff,性能不佳。
更符合當(dāng)前應(yīng)用的需求:純展示且無需動(dòng)畫過渡等。
實(shí)現(xiàn)更簡(jiǎn)單
將原本繁重的 DOM 操作轉(zhuǎn)移到了 JavaScript 的拼接字符串后,性能得到了很大提升(不會(huì)導(dǎo)致程序卡死而渲染不出視圖)。這種實(shí)現(xiàn)原理難道不就是 Vue、React 等框架解決的問題之一嗎?只不過框架考慮的場(chǎng)景更廣,有些地方需要我們自己根據(jù)實(shí)際情況進(jìn)行優(yōu)化而已。
在瀏覽器當(dāng)中,JavaScript 的運(yùn)算在現(xiàn)代的引擎中非??欤?DOM 本身是非常緩慢的東西。當(dāng)你調(diào)用原生 DOM API 的時(shí)候,瀏覽器需要在 JavaScript 引擎的語境下去接觸原生的 DOM 的實(shí)現(xiàn),這個(gè)過程有相當(dāng)?shù)男阅軗p耗。所以,本質(zhì)的考量是,要把耗費(fèi)時(shí)間的操作盡量放在純粹的計(jì)算中去做,保證最后計(jì)算出來的需要實(shí)際接觸真實(shí) DOM 的操作是最少的。 —— 《Vue 2.0——漸進(jìn)式前端解決方案》
當(dāng)然,由于 JavaScript 天生單線程,即使執(zhí)行數(shù)速度再快,也會(huì)導(dǎo)致頁面有短暫的時(shí)間拒絕用戶的輸入。此處可通過 Web Worker 或其它方式解決,這也將是我們后續(xù)講到的問題。
也有網(wǎng)友提供了優(yōu)化大量列表的方法:https://clusterize.js.org/。 但在這里我并沒有采用此方式。
強(qiáng)大的 GPU 加速插入 DOM 后,又會(huì)出現(xiàn)了另外一個(gè)問題:滾動(dòng)會(huì)很卡。猜想這是渲染問題,畢竟 34 萬個(gè)單元格同時(shí)存在于界面中。
添加 transform: translate3d(0, 0, 0) / translateZ(0) 屬性啟動(dòng) GPU 渲染,即可解決這個(gè)渲染性能問題。再次感嘆該屬性的強(qiáng)大。?
后來,考慮到用戶并不需要查看全部數(shù)據(jù),只需展示部分?jǐn)?shù)據(jù)讓用戶進(jìn)行參考即可。我們對(duì)此只渲染前 30/50 行數(shù)據(jù)。這樣即可提升用戶體驗(yàn),也能進(jìn)一步優(yōu)化性能(又是純屬臆測(cè))。
記得關(guān)閉 Vuex 的嚴(yán)格模式另外,由于自己學(xué)藝不精和粗心大意,忘記在生產(chǎn)環(huán)境關(guān)閉 Vuex 的『嚴(yán)格模式』。
Vuex 的嚴(yán)格模式要在生產(chǎn)中關(guān)閉,否則會(huì)對(duì) state 樹進(jìn)行一個(gè)深觀察 (deep watch),產(chǎn)生不必要的性能損耗。也許在數(shù)據(jù)量少時(shí),不會(huì)注意到這個(gè)問題。
我當(dāng)時(shí)的情況是:導(dǎo)入 Excel 數(shù)據(jù)后,再進(jìn)行交互(涉及 Vuex 的讀寫操作),則需要等幾秒才會(huì)響應(yīng),而直接通過純 DOM 監(jiān)聽的事件則無此問題。由此,判斷出是 Vuex 問題。
const store = new Vuex.Store({ // ... strict: process.env.NODE_ENV !== "production" })多進(jìn)程!?。?/b>
前面說道,JavaScript 天生單線程,即使再快,對(duì)于需要處理數(shù)據(jù)量較大的情況,也會(huì)出現(xiàn)拒絕響應(yīng)的問題。因此需要 Web Worker 或類似的方案去解決。
在這里我不選擇 Web worker 的原因有如下幾點(diǎn):
有其它更好的替代方案:一個(gè)主進(jìn)程能創(chuàng)建多個(gè)渲染進(jìn)程,通過 IPC 即可進(jìn)行數(shù)據(jù)交互;
Electron 不支持 Web Worker!
Electron 作者在 2014.11.7 在《state of web worker support?》 issue 中回復(fù)了以下這一段:
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.
因此,我們最終采用了創(chuàng)建一個(gè)新的渲染進(jìn)程 background process 進(jìn)行處理數(shù)據(jù)。由 Electron 章節(jié)可知,每個(gè) Electron 渲染進(jìn)程是獨(dú)立的,因此它們不會(huì)互相影響。但這也帶來了一個(gè)問題:它們不能相互通訊?
錯(cuò)!下面有 3 種方式進(jìn)行通訊:
Storage API:對(duì)某個(gè)標(biāo)簽頁的 localStorage/sessionStorage 對(duì)象進(jìn)行增刪改時(shí),其他標(biāo)簽頁能通過 window.storage 事件監(jiān)聽到。
IndexedDB:IndexedDB 是一個(gè)為了能夠在客戶端存儲(chǔ)可觀數(shù)量的結(jié)構(gòu)化數(shù)據(jù),并且在這些數(shù)據(jù)上使用索引進(jìn)行高性能檢索的 API。
通過主進(jìn)程作為中轉(zhuǎn)站:設(shè)主界面的渲染進(jìn)程是 A,background process 是 B,那么 A 先將 Excel 數(shù)據(jù)傳遞到主進(jìn)程,然后主進(jìn)程再轉(zhuǎn)發(fā)到 B。B 處理完后再原路返回,具體如下圖。當(dāng)然,也可以將數(shù)據(jù)存儲(chǔ)在主進(jìn)程中,然后在多個(gè)渲染進(jìn)程中使用 remote 模塊來訪問它。
該工具采用了第三種方式的第一種情況:
1、主頁面渲染進(jìn)程 A 的代碼如下:
//① ipcRenderer.send("filter-start", { filterTagList: this.filterTagList, filterWay: this.filterWay, curActiveSheetName: this.activeSheet.name }) // ⑥ 在某處接收 filter-response 事件 ipcRenderer.on("filter-response", (arg) => { // 得到處理數(shù)據(jù) })
2、作為中轉(zhuǎn)站的主進(jìn)程的代碼如下:
//② 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、處理繁重?cái)?shù)據(jù)的 background process 渲染進(jìn)程 B 的代碼如下:
// ③ ipcRenderer.on("filter-start", (event, arg) => { // 進(jìn)行運(yùn)算 ... // ④ 運(yùn)算完畢后,再通過 IPC 原路返回。主進(jìn)程和渲染進(jìn)程 A 也要建立相應(yīng)的監(jiān)聽事件 ipcRenderer.send("filter-response", { filRow: tempFilRow }) })
至此,我們將『讀取文件』、『過濾數(shù)據(jù)』和『導(dǎo)出文件』三大耗時(shí)的數(shù)據(jù)操作均轉(zhuǎn)移到了 background process 中處理。
這里,我們只創(chuàng)建了一個(gè) background process,如果想要做得更極致,我們可以新建『CPU 線程數(shù)- 1 』 個(gè)的 background process 同時(shí)對(duì)數(shù)據(jù)進(jìn)行處理,然后在主進(jìn)程對(duì)處理后數(shù)據(jù)進(jìn)行拼接,最后再將拼接后的數(shù)據(jù)返回到主頁面的渲染進(jìn)程。這樣就可以充分榨干 CPU 了。當(dāng)然,在此我不會(huì)進(jìn)行這個(gè)優(yōu)化。
內(nèi)存占有量過大不要為了優(yōu)化而優(yōu)化,否則得不償失。 —— 某網(wǎng)友
解決了執(zhí)行效率和渲染的問題,發(fā)現(xiàn)也存在內(nèi)存占用量過大的問題。當(dāng)時(shí)猜測(cè)是以下幾個(gè)原因:
三大耗時(shí)操作均放置在 background process 處理。在通訊傳遞數(shù)據(jù)的過程中,由于不是共享內(nèi)存(因?yàn)?IPC 是基于 Socket 的),導(dǎo)致出現(xiàn)多份數(shù)據(jù)副本(在寫該篇文章時(shí)才有了這相對(duì)確切的答案)。
Vuex 是以一個(gè)全局單例的模式進(jìn)行管理,但它會(huì)是不是對(duì)數(shù)據(jù)做了某些封裝,而導(dǎo)致性能的損耗呢?
由于 JavaScript 目前不具有主動(dòng)回收資源的能力,所以只能主動(dòng)對(duì)閑置對(duì)象設(shè)置為 null,然后等待 GC 回收。
由于 Chromium 采用多進(jìn)程架構(gòu),因此會(huì)涉及到進(jìn)程間通信問題。Browser 進(jìn)程在啟動(dòng) Render 進(jìn)程的過程中會(huì)建立一個(gè)以 UNIX Socket 為基礎(chǔ)的 IPC 通道。有了 IPC 通道之后,接下來 Browser 進(jìn)程與 Render 進(jìn)程就以消息的形式進(jìn)行通信。我們將這種消息稱為 IPC 消息,以區(qū)別于線程消息循環(huán)中的消息。
——《Chromium的IPC消息發(fā)送、接收和分發(fā)機(jī)制分析》
定義:為了易于理解,以下『Excel 數(shù)據(jù)』均指 Excel 的全部有效單元格轉(zhuǎn)為 JSON 格式后的數(shù)據(jù)。
最容易處理的無疑是第三點(diǎn),手動(dòng)將不再需要的變量及時(shí)設(shè)置為 null。但這效果并不明顯。
后來,通過系統(tǒng)的『活動(dòng)監(jiān)視器』對(duì)該工具的每階段(打開時(shí)、導(dǎo)入文件時(shí)、篩選時(shí)和導(dǎo)出時(shí))進(jìn)行粗略的內(nèi)存分析,得到以下報(bào)告(之前分析的、未作修改):
---------------- S:報(bào)告分割線 ----------------
經(jīng)觀察,主要耗內(nèi)存的是頁面進(jìn)程。下面通過截圖說明:
PID 15243 是主進(jìn)程
PID 15246 是頁面渲染進(jìn)程
PID 15248 是 background 渲染進(jìn)程
a、首次啟動(dòng)程序時(shí)(第 4 行是主進(jìn)程;第 1 行是頁面渲染進(jìn)程;第 3 行是 background 渲染進(jìn)程 )
b、導(dǎo)入文件(第 5 行是主進(jìn)程;第 2 行是頁面渲染進(jìn)程;第 4 行是 background 渲染進(jìn)程 )
c、篩選數(shù)據(jù)(第 4 行是主進(jìn)程;第 1 行是頁面渲染進(jìn)程;第 3 行是 background 渲染進(jìn)程 )
由于 JS 目前不具有主動(dòng)回收資源的功能,所以只能主動(dòng)將對(duì)象設(shè)置為 null,然后等待 GC 回收。
因此,經(jīng)過一段時(shí)間等待后,內(nèi)存占用如下:
d、一段時(shí)間后(第 4 行是主進(jìn)程;第 1 行是頁面渲染進(jìn)程;第 3 行是 background 渲染進(jìn)程 )
由上述可得,頁面渲染進(jìn)程由于頁面元素和 Vue 等 UI 相關(guān)資源是固定的,占用內(nèi)存較大且不能回收。主進(jìn)程占用資源也不能得到很好釋放,暫時(shí)不知道原因,而 background 渲染進(jìn)程則較好地釋放資源。
---------------- E:報(bào)告分割線 ----------------
根據(jù)報(bào)告,初步得出的結(jié)論是 Vue 和通訊時(shí)占用資源較大。
根據(jù)該工具的實(shí)際應(yīng)用場(chǎng)景:由于 Excel 數(shù)據(jù)只在『導(dǎo)入』和『過濾后』兩個(gè)階段需要展示,而且展示的只是通過 JavaScript 拼接的 HTML 字符串構(gòu)成的 DOM 而已。因此將表格數(shù)據(jù)放置在 Vuex 中,有點(diǎn)濫用資源的嫌疑。
另外,在 background process 中也有存有一份 Excel 數(shù)據(jù)副本。因此,索性只在 background process 存儲(chǔ)一份 Excel 數(shù)據(jù),然后每當(dāng)數(shù)據(jù)變化時(shí),通過 IPC 讓 background process 返回拼接好的 HTML 字符串即可。這樣一來,內(nèi)存占有量立刻下降許多。而且這也是一個(gè)一舉多得的優(yōu)化:
字符串拼接操作也轉(zhuǎn)移到了 background process,頁面的渲染進(jìn)程進(jìn)一步減少耗時(shí)的操作;
內(nèi)存占有量大大減小,響應(yīng)速度也得到了提升。
其實(shí),這也有點(diǎn)像 Vuex 的『全局單例模式管理』,一份數(shù)據(jù)就好。
當(dāng)然,對(duì)于 Excel 的基本信息,如行列數(shù)、SheetName、標(biāo)題組等均依然保存在 Vuex。
優(yōu)化后的內(nèi)存占有量如下圖。與上述報(bào)告的第三張圖相比(同一階段),內(nèi)存占有量下降了 44.419%:
另外,對(duì)于不需要響應(yīng)的數(shù)據(jù),可通過 Object.freeze() 凍結(jié)起來。這也是一種優(yōu)化手段。但該工具目前并沒有應(yīng)用到。
至此,優(yōu)化部分也闡述完畢了!
該工具目前是開源的,歡迎大家使用或推薦給用研組等有需要的人。
你們的反饋(可提交 issues / pull request)能讓這個(gè)工具在使用和功能上不斷完善。
最后,感謝 LV 在產(chǎn)品規(guī)劃、界面設(shè)計(jì)和優(yōu)化上的強(qiáng)力支持。全文完!
感謝您的閱讀,本文由 Jc 原創(chuàng)提供。如若轉(zhuǎn)載,請(qǐng)注明出處:凹凸實(shí)驗(yàn)室(https://aotu.io/notes/2016/11...)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/91191.html
摘要:前言本項(xiàng)目旨在從零到壹,制作一款界面精美的聊天軟件。因?yàn)楸救耸情_發(fā),設(shè)計(jì)功底欠缺,所以軟件設(shè)計(jì)的有點(diǎn)丑,如果有大神有更好的,歡迎。 Hola 前言 本項(xiàng)目旨在從零到壹,制作一款界面精美的聊天軟件。 Github 地址因?yàn)橐压ぷ鳎钥赡軟]有多少時(shí)間來繼續(xù)跟進(jìn)這個(gè)項(xiàng)目了,項(xiàng)目可優(yōu)化的點(diǎn)已在下文列出,歡迎大家 Fork 或 Star。 ps: 征 logo 一枚。因?yàn)楸救耸情_發(fā),設(shè)計(jì)功底...
摘要:楊冀龍是安全焦點(diǎn)民間白帽黑客組織核心成員,被浪潮之巔評(píng)為中國新一代黑客領(lǐng)軍人物之一他在本文中依次分享了對(duì)于黑客的定義如何從黑客成為一名安全創(chuàng)業(yè)者技術(shù)創(chuàng)業(yè)踩過的坑給技術(shù)創(chuàng)業(yè)者建議等內(nèi)容。 showImg(https://segmentfault.com/img/remote/1460000012377230?w=1240&h=796); 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為...
摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁性能管理詳解離線緩存簡(jiǎn)介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對(duì)象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...
摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁性能管理詳解離線緩存簡(jiǎn)介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對(duì)象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...
閱讀 1131·2021-11-19 09:40
閱讀 975·2021-11-12 10:36
閱讀 1273·2021-09-22 16:04
閱讀 3117·2021-09-09 11:39
閱讀 1277·2019-08-30 10:51
閱讀 1891·2019-08-30 10:48
閱讀 1232·2019-08-29 16:30
閱讀 475·2019-08-29 12:37