一、簡介
從 WPS 2021 版本開始,WPS 正式支持使用 JS 作為宏語言,官方稱 JSA(報錯時,用得就是這個名稱),亦即 JS for Application 的縮寫。
根據官方文檔(https://open.wps.cn/docs/office)中的介紹,WPS 內嵌了一個 V8 引擎的 JavaScript 運行時,支持大部分 ES6 語法,實測支持到 ES2019:
WPS宏編輯器集成了一個V8 引擎的 JavaScript 運行時,支持大部分ES6語法,因此宏編輯器支持JavaScript 標准內置對象,注意,JS內置對象和瀏覽器的內置對象是不同的,WPS宏編輯器集成的是JavaScript 運行時,而不是瀏覽器,因此WPS宏編輯器不支持瀏覽器的內置對象。具體API參見https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects 。
JSA 也是 WPS 默認的宏語言,WPS 官方為之提供了 WPS 宏編輯器,以方便大家編輯 JSA 宏代碼。
二、宏錄制
如下示例是要給 B4 單元格字體顏色設為紅色,背景色設置為黃色,錄制的宏代碼如下:
1 /** 2 * Macro1 Macro 3 * 宏由 nutix 錄制,時間: 2021/07/17 4 */ 5 function Macro1() 6 { 7 Range("B4").Select(); 8 (obj=>{ 9 obj.Color = 255; 10 obj.TintAndShade = 0; 11 })(Selection.Font); 12 (obj=>{ 13 obj.Pattern = xlPatternSolid; 14 obj.Color = 65535; 15 obj.TintAndShade = 0; 16 obj.PatternColorIndex = -4105; 17 })(Selection.Interior); 18 19 }
第 8-11 和 第 12-17 行處的代碼段,是定義箭頭函數,以及對它們的調用。
宏錄制器,總是會把對同一個對象的多個操作,以這種箭頭函數的形式,錄制出來。
這是復合寫法,手工寫 JSA 宏代碼,肯定是不提倡這種寫法的。
三、事件
這可能是大家打交道最頻繁的,與 VBA 有所不同的是,現在沒有文檔模塊了,WPS 宏編輯器只提供了一個入口來編寫事,即
它訂閱了當前工作簿 Workbook
的 SheetChange
事件,當第 1/2 表的第 4 列的值發生改變時,將單元格的字體顏色修改為藍色,它的代碼如下:
1 function Workbook_SheetChange(Sh, rg) 2 { 3 if ([1, 2].indexOf(Sh.Index) != -1) { 4 //對第1、2表D列的單元格值變更進行處理 5 const blue = 12611584; 6 if (rg.Column == 4) { 7 rg.Font.Color = blue; 8 } 9 } 10 }
與 VBA 的事件有所不同,WPS 中不內建文檔模塊,這里說的文檔模塊,就是你在 Office 的 VBE 中【工程管理器】里面的 Sheet1, Sheet2, ..., SheetN 和 ThisWorkbook,這 N+1 個內建的模塊,VBE 里針對每一個打開的工作簿,都會內建一堆這樣的文檔模塊,工作表及打開的工作簿如果比較多時,要查找自己的模塊,還得去拖滾動條。
WPS 則以比較巧妙的方式,規避了這個問題:WPS 不內建任何模塊,用戶想要訂閱事件,直接在事件欄里面選中即可,事件處理程序名稱與參數,會更好地協助你做你想做的事兒。
四、用戶窗體
WPS貼心地為大家新提供了幾個控件,尤其是【水平布局控件】和【垂直布局控件】,有這兩個控件,大家可以更好的設計窗體了。
要編寫窗體及其控件的事件,和文檔事件一樣,通過事件欄,來指定要處理的控件的事件
由圖可見WPS窗體的界面與代碼是分離的,代碼是寫在普通模塊里面的,繪制的窗體是在另一個模塊
由上圖可見,所有的對象,無論是Application(應用程序),Workbook(工作簿)、窗體、窗體控件、工作表控件,這些事件源,都是在同一個列表中,大家注意命名,以免混淆
五、自定義公式函數
為表格自定義公式函數是很簡單的,只要返回常規類型(如文本,數字,日期,真假值即可),且是全局函數即可
1 /*刪除給定文本的某字符及后面的字符 2 target : 要處理的目標 3 from : 要刪除的起始字符 4 */ 5 function DeleteFrom(target, from) { 6 let value; 7 if (target.constructor.name == 'Range') { 8 //本公式只接受一個單元格引用 9 if (target.Areas.Count > 1 || 10 target.Areas.Item(1).Cells.Count > 1) 11 throw new Error('本函數只能處理一個單元格'); 12 else 13 value = target.Value().toString(); 14 } else 15 value = target.toString(); 16 17 if (from.constructor.name == 'Range') { 18 if (from.Areas.Count > 1 || 19 from.Areas.Item(1).Cells.Count > 1) 20 throw new Error('當 from 參數是一個單元格' + 21 '引用時,請確保它只包含一個單元格'); 22 from = from.Value().toString(); 23 } 24 else 25 from = from.toString(); 26 27 28 let index = value.indexOf(from); 29 if (index == -1) 30 return value; 31 else 32 return value.substr(0, index); 33 } 34 35 /*將單元格區域數據轉換成一個基於文本的表 36 target : 要處理的單元格區域 37 sep : 分隔符 38 */ 39 function ToTextTable(target, sep = ',') { 40 if (target.constructor.name != 'Range') 41 throw new TypeError('target 參數必須是一個單元格區域'); 42 43 sep = sep.toString(); 44 let values = new Array(); 45 for (let iRow = 1; iRow <= target.Rows.Count; iRow++) { 46 let rowValues = new Array(); 47 for (let iColumn = 1; iColumn <= target.Columns.Count; iColumn++) { 48 rowValues.push(target.Cells.Item(iRow, iColumn).Value()); 49 } 50 values.push(rowValues.join(sep)); 51 } 52 return values.join('\n'); 53 }
六、WPS宏編輯器與代碼調試
- WPS 宏編輯器,提供了【立即窗口】,您可以直接在【立即窗口】執行單行的 JSA 語句,就像在寫命令行,與 VBA 不同的時,這里不需要前置問號才算求值
- WPS 宏編輯器,允許你為 JSA 代碼行設置斷點,以方便對代碼的調試,配套提供了【本地窗口】,以方便你了解斷點處的運行狀態
- WPS 宏編輯器,同樣提供了【逐語句】【逐過程】【跳出】功能,您可以以 VBA 熟悉的方式來調試 JSA 代碼,不同的是它們的快捷鍵與 VBA 不同,與現在主流的 IDE 相同
- WPS 宏編輯器,同樣提供了【編譯】工具,你可以在寫完代碼后,用它來做語法檢查
- WPS 宏編輯器,同樣提供了【監視】功能,並配套提供了【監視窗口】,你可以借此設置中斷條件,以進行復雜調試
- 可以通過【文件】》【導入/出】,來導入/出代碼模塊
- 可以在代碼行通過:右鍵》【切換書簽】的方式,來建立書簽,使用 Ctrl + Shift + N(Next:下一個)/P(Previous:前一個)快捷鍵,快速地在多個書簽之間跳轉,這對於調用自定義的庫來實現具體應用時比較有用,可以方便地在庫與調用代碼之間來回切換,調整庫代碼與應用代碼,以便可以有更可靠更高效的代碼邏輯
- 通過【插入】》【文件】,可以在光標所在處快速插入外部文件中的代碼或數據
- Console 對象是 JS 調試時的重要工具,JSA 環境下的 Console 是指向立即窗口的,你可以通過 Console.log(text) 向立即窗口寫數據,也可以通過 Console.clear() 清除它的內容,這控制力比 VBE 強多了
- Debug 對象,可以通過 Debug.GC(),要求引擎進行變量垃圾回收;通過 Debug.Print(...) 實現與 Console.log(text) 相同的功能
七、對象成員訪問方式上的一些變動
- 有參數的屬性,在 JSA 中變成了方法,須得在后面加 "()" ,才能正確訪問,比如
Address
在 VBA 中是屬性,因為 VBA 支持帶參數的屬性,但 JSA 不支持,所以在 JSA 中Address
成員,被識別為方法,必須以執行函數的方式來訪問;當你參考 Office 文檔來寫 JSA 代碼時,要注意,只要需要傳遞參數,即便手冊說它是屬性,在 JSA 中也應被當然方法來對待。 - VBA 中方法和屬性不使用參數時,括號是可以省略的,比如
Worksheet.OLEObjects(...)
方法,VBA 中得到OLEObjects
集合的方式是Set objs = Sheet1.OLEObjects
,不加括號,可以正確訪問;但 JSA 中必須加括號才能訪問,即應寫成let objs = Worksheets.Item('Sheet1').OLEObjects();
- VBA 調用方法或有參屬性時,如果某個參數有默認值,可以直接省略該位置的參數,比如
ActiveCell.Address(,,xlR1C1)
,JSA 中不允許這么做,如果想使用默認值,則應寫成ActiveCell.Address(undefined, undefined, xlR1C1)
- VBA 可以以命名參數的方式,向方法或屬性傳遞參數,如
ActiveCell.Address(ReferenceStyle:=xlR1C1)
,這種語法在 JSA 中是不支持的,你必須寫成ActiveCell.Address(undefined, undefined, xlR1C1)
; - 當參數比較多,你又只想傳遞一個參數,其它都用默認值的時候,怎么辦呢,可以這樣:
ActiveCell.Address(...[,,xlR1C1])
; - 可能你又要說,如果參數幾十個,只想傳遞某個參數呢?也可以,比如
ThisWorkbook.SaveAs(/*有 12 個參數*/)
,每個參數都有默認值,我們想傳遞第 9 個參數,也即AddToMru
參數,可以這樣寫:1 function Save() { 2 let args = []; 3 args[8] = true; 4 ThisWorkbook.SaveAs(...args); 5 }
八、JSA 相較於 VBA 的優勢
- JSA 基於原型鏈的面向對象,靈活性非常強,但畢竟動態一時爽,重構火葬場,其工程性差,好在它所面臨的業務 99.99% 的場景,是弱工程性的,所以正好適用
- JSA 背靠 JS,后者有國際性的標准化組織,每年推動語言改進,JS 只會越來越強大,越來越方便,經典 class 的引入就是明證
- JSA 背靠 JS,后者有最活躍的社區,由全球人才發力,各種工具與庫層出不窮,可以為 JSA 提供助力,比如我使用了來自 github 的
linq.js
庫,它極大地方便了一些查詢工作- VBA 長久沒有語言層面的更新,語言特性早已過時;而現行的 VBA 標准,其語言特性呆板、生硬,基本沒什么靈活性,比如不能愉快地相互傳遞函數,來靈活地配置功能(借助奇技淫巧曲線救國的,一邊去)
- VBE 宏編輯器,長久沒有得到更新,編程體驗奇差:
- 在編輯的模塊窗口是 MDI 窗口模式,而不是便利的標簽頁模式,關閉與切換都很麻煩
- 標識符命名具有穿透性,寫得好好的庫 API 命名,會因為其它模塊同名標識符的大小寫,被自動修改,真是日了狗了
- Excel 表格太多時,工程管理器里面內建的文檔模塊能排到“天邊”
- JSA 安全性好,沒有對亂七八糟的外部庫的支持,你做好 Office 自動化這個本職工作就好,調用
Win32API
?,你想干嘛- JSA 跨平台,JS 能跨,JSA 就能跨,基於 V8 好破浪(乘涼)