JavaScript for Automation(JXA) 入

2018-09-19  本文已影响167人  ltryee

从 macOS 10.10 开始(当时还叫 OS X),苹果提供了一种新的使用 JavaScript 语言编写自动化脚本的方式,和已有的 AppleScript 一样,能够方便我们操作其他应用,自动化地处理一些事务。然而使用 JavaScript 编写代码要比 AppleScript 容易得多。

0. 学习一个新工具的时候,我们到底在学什么?

在了解 JXA 之前,先考虑一个问题:当决定要学习使用 JXA 的时候,我们到底需要学习什么?不仅是本文档介绍的 JXA,学习任何一个新工具,都逃不过这个问题:

1. JXA 基本概念

在日常工作中,我们通常需要处理很多复杂、重复且耗时的任务。通过编写脚本,我们可以自动化地处理一些与应用、进程和操作系统交互的事务,从而提高效率,减少错误,节约时间。

脚本是如何工作的

开放脚本架构(Open Scripting Architecture, OSA)为 OS X 提供了夸应用通信的标准和扩展机制,这种通信发生于应用(Applecations)之间交换苹果事件(Apple Events)。Apple Events 即一种封装了命令和数据的进程间通信(interprocess message)

一个脚本化应用(scriptable application)通过执行操作或提供数据来响应 Apple events。每个脚本化应用都实现了自己的脚本功能,并通过脚本字典(scripting dictionary)公开自己独特的术语。

在 OS X 中,OSA 提供以下能力:

开放脚本框架(Open Scripting framework)中定义了用于创建脚本组件(scripting components)的标准数据结构、程序和资源。该框架同时提供API,用于编译、执行、加载和存储脚本。
苹果事件管理器(Apple Event Manager)为创建脚本化应用提供基础支持,由 CoreServices 框架内的 AE 框架实现。开发者可以通过 Foundation 框架中的 Apple Event API 与 Apple Event Manager 进行交互。

下图展示了上述组件如何在操作系统中协同工作[1]

The Open Scripting Architecture workflow

脚本的类型有哪些

在 Mac 上,编写自动化脚本主要使用 AppleScript 和 JavaScript。本文档将从零开始介绍如何使用 JavaScript 编写自动化脚本。

2. 安装工具

从 macOS 10.10 开始,苹果为 OSA 加入了 JavaScript 的支持,在已有能够运行 AppleScript 脚本的地方,都可以运行 JavaScript 编写的脚本。因此我们不需要安装额外的任何软件,使用Script Editor应用或命令行使用osascript命令即可运行 JavaScript 脚本。

3. 开发环境

Script Editor

打开脚本编辑器应用/Applications/Utilities/Script Editor.app

Hello world

新建一个脚本,输入

console.log('hello world')

点击运行:


image

寻求帮助

如果我们需要查找某个应用的脚本字典,在状态栏点击文件->打开词典...,在弹出的对话框中选择应用。

脚本字典

REPL

执行命令osascript -il JavaScript。其中-i表示交互模式,-l JavaScript表示使用 JavaScript ,不加该标签则默认使用 AppleScript。

➜  ~ osascript -il JavaScript
>> console.log('hello world.')
hello world.
=> undefined
>> 

Shell Script

执行命令osascript -l JavaScript -e '$.NSLog("hello ObjC")':

➜  ~ osascript -l JavaScript -e '$.NSLog("hello ObjC")'
2018-09-18 16:43:31.327 osascript[35632:34312370] hello ObjC

Shebang Script

创建文件并保存到./test.js

#!/usr/bin/env osascript -l JavaScript

function run(argv) {
  console.log(JSON.stringify(argv))
}

执行命令./test.js -a -b -c -d lll

➜  ~ ./test.js -a -b -c -d lll
["-a","-b","-c","-d","lll"]

osascript -l JavaScript ./test.js -a aaa -b bbb

➜  ~ osascript -l JavaScript ./test.js -a aaa -b bbb
["-a","aaa","-b","bbb"]

4. 编程语言

JavaScript 本身无需多言。值得关注的是,OSA 对 JavaScript 功能的支持取决于 macOS 的版本。JXA 使用的 JavaScriptCore 引擎版本似乎与 macOS 捆绑的 Safari 版本相对应[2]。参阅 kangax's compatibility table 并找到 SF 列查看不同 Safari 版本的支持情况。

5. 框架,全局对象,第三方库以及其他……

脚本中的事件处理函数(Event Handlers)

脚本中实现特定的名字的函数,可以接收特定的事件:

全局 JS 对象

作为 JavaScript 的执行环境,JXA 包含了以下全局变量[3]

JXA 与 ObjC

这是最激动人心的部分。

加载 OC 框架到 JS 上下文

JXA通过内置的 Objective-C Bridge 调用 ObjC 接口。上一节提到的ObjC$是用来调用 Objective-C Bridge 全局对象。
其中ObjC对象提供接口用于 JS 引擎访问/操作 OC 对象,而$则是 OC 对象在 JS 上下文中的映射。看不懂这句话也没关系,举个例子,使用ObjC.import()函数加载外部框架:

ObjC.import('Cocoa')
$.NSBeep()

如果直接执行$.NSBeep(),会收到错误:

>> $.NSBeep()
!! Error on line 1: TypeError: $.NSBeep is not a function. (In '$.NSBeep()', '$.NSBeep' is undefined)

这是因为NSBeep()是 Cocoa 框架的一部分,我们在使用该函数前需要调用ObjC.import('Cocoa')将函数加载到 JS 上下文中。

调用 OC 方法的 JS 语法

举个例子,下面的 OC 代码:

NSRect frame = NSMakeRect(0, 0, 200, 200);
NSWindow* window  = [[NSWindow alloc] initWithContentRect:frame
                    styleMask:NSBorderlessWindowMask
                    backing:NSBackingStoreBuffered
                    defer:NO]

在 JXA 中这样实现:

var styleMask = $.NSTitledWindowMask | $.NSClosableWindowMask | $.NSMiniaturizableWindowMask;
var frame = $.NSMakeRect(0, 0, windowWidth, windowHeight);
var window = $.NSWindow.alloc.initWithContentRectStyleMaskBackingDefer(
  frame,
  styleMask,
  $.NSBackingStoreBuffered,
  false
);

引用调用传参

NSFileManager类的方法- (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory;,第二个参数的类型为BOOL *,在 OC 中我们通常使用引用调用来传递这个参数:

BOOL isDir;
NSFileManager *fileManager = [[NSFileManager alloc] init];
[fileManager fileExistsAtPath:fontPath isDirectory:&isDir];

在 JXA 中可以使用Ref()函数获取一个变量,该变量可以传给需要引用调用的参数。调用完成后,变量数据通过isDirRef[0]返回:

var isDirRef = Ref()  //set up a variable which can be passed by reference to ObjC functions.
$.NSFileManager.alloc.init.fileExistsAtPathIsDirectory("/Users/Sancarn/Desktop/D.png", isDir)
var isDir = isDirRef[0]     //get the data from the variable

参考资料

我的博客即将同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2tynnwdd44ysg


  1. Mac Automation Scripting Guide

  2. JavaScript for Automation Cookbook

  3. JavaScript for Automation Release Notes

上一篇下一篇

猜你喜欢

热点阅读