在 Google Apps 脚本和 JavaScript 中,运行时或运行时环境包含用于解析和执行脚本代码的 JavaScript 引擎。运行时提供有关内存访问方式、程序与计算机操作系统交互方式以及合法程序语法的规则。每个网络浏览器都有一个 JavaScript 运行时环境。
从历史上看,Apps 脚本一直由 Mozilla 的 Rhino JavaScript 解释器提供支持。虽然 Rhino 为 Apps 脚本执行开发者脚本提供了一种便捷的方式,但也将 Apps 脚本与特定 JavaScript 版本 (ES5) 绑定在一起。使用 Rhino 运行时环境的脚本中,Apps 脚本开发者无法使用更现代的 JavaScript 语法和功能。
为了解决此问题,Apps 脚本现在由 V8 运行时提供支持,该运行时为 Chrome 和 Node.js 提供技术支持。将现有脚本迁移到 V8,以便利用新式 JavaScript 语法和功能。
本页介绍了 V8 启用的新功能,以及如何启用 V8 以在脚本中使用。将脚本迁移到 V8 介绍了将现有脚本迁移到 V8 运行时的步骤。
V8 运行时的功能
使用 V8 运行时的脚本能够利用以下功能:
现代 ECMAScript 语法
在由 V8 运行时提供支持的脚本中使用新式 ECMAScript 语法。此语法包括 let、const 和许多其他热门功能。
如需简要了解可以使用 V8 运行时进行的常用语法改进,请参阅 V8 语法示例。
与其他常见的 JavaScript 运行时相比,Apps 脚本 V8 运行时存在一些限制和主要差异。如需了解详情,请参阅 Apps 脚本 V8 运行时限制。
改进了功能检测
改进了对使用 V8 的脚本的 Apps 脚本函数检测。 新运行时可识别以下函数定义格式:
function normalFunction() {} async function asyncFunction() {} function* generatorFunction() {} var varFunction = function() {} let letFunction = function() {} const constFunction = function() {} var namedVarFunction = function alternateNameVarFunction() {} let namedLetFunction = function alternateNameLetFunction() {} const namedConstFunction = function alternateNameConstFunction() {} var varAsyncFunction = async function() {} let letAsyncFunction = async function() {} const constAsyncFunction = async function() {} var namedVarAsyncFunction = async function alternateNameVarAsyncFunction() {} let namedLetAsyncFunction = async function alternateNameLetAsyncFunction() {} const namedConstAsyncFunction = async function alternateNameConstAsyncFunction() {} var varGeneratorFunction = function*() {} let letGeneratorFunction = function*() {} const constGeneratorFunction = function*() {} var namedVarGeneratorFunction = function* alternateNameVarGeneratorFunction() {} let namedLetGeneratorFunction = function* alternateNameLetGeneratorFunction() {} const namedConstGeneratorFunction = function* alternateNameConstGeneratorFunction() {} var varLambda = () => {} let letLambda = () => {} const constLambda = () => {} var varAsyncLambda = async () => {} let letAsyncLambda = async () => {} const constAsyncLambda = async () => {}
从触发器和回调中调用对象方法
使用 V8 的脚本可以从您已能调用库方法的位置调用对象方法和类静态方法。这些地点包括:
- Google Workspace 加购项清单触发器
- 可安装的触发器
- Google Workspace 编辑器中的菜单项
- 用户回调函数,例如
ScriptApp.newStateToken()代码示例中描述的函数。
以下 V8 示例展示了在 Google 表格中构建菜单项时如何使用对象方法:
function onOpen() {
const ui = SpreadsheetApp.getUi(); // Or DocumentApp, SlidesApp, or FormApp.
ui.createMenu('Custom Menu')
.addItem('First item', 'menu.item1')
.addSeparator()
.addSubMenu(ui.createMenu('Sub-menu')
.addItem('Second item', 'menu.item2'))
.addToUi();
}
const menu = {
item1: function() {
SpreadsheetApp.getUi().alert('You clicked: First item');
},
item2: function() {
SpreadsheetApp.getUi().alert('You clicked: Second item');
}
}
查看日志
Apps 脚本提供两种日志记录服务:Logger 服务和 console 类。这两项服务都会将日志写入同一 Stackdriver Logging 服务。
如需显示 Logger 和 console 日志,请点击脚本编辑器顶部的执行日志。
查看执行情况
如需查看脚本的执行历史记录,请打开 Apps 脚本项目,然后在左侧点击执行图标 。
执行面板不提供各个 Apps 脚本服务调用的带时间戳的日志。使用 console 服务创建适当的日志消息。使用 console 创建的所有日志都会显示在 Executions 面板中。
V8 语法示例
下面简要列出了使用 V8 运行时的脚本可用的热门语法功能。
let和const
let 和 const 关键字分别用于定义块作用域局部变量和块作用域常量。
// V8 runtime let s = "hello"; if (s === "hello") { s = "world"; console.log(s); // Prints "world" } console.log(s); // Prints "hello" const N = 100; N = 5; // Results in TypeError |
箭头函数
箭头函数提供了一种在表达式中定义函数的简洁方式。
// Rhino runtime function square(x) { return x * x; } console.log(square(5)); // Outputs 25 |
// V8 runtime const square = x => x * x; console.log(square(5)); // Outputs 25 // Outputs [1, 4, 9] console.log([1, 2, 3].map(x => x * x)); |
类
类提供了一种通过继承从概念上整理代码的方法。V8 中的类主要是基于 JavaScript 原型的继承的语法糖。
// V8 runtime class Rectangle { constructor(width, height) { // class constructor this.width = width; this.height = height; } logToConsole() { // class method console.log(`Rectangle(width=${this.width}, height=${this.height})`); } } const r = new Rectangle(10, 20); r.logToConsole(); // Outputs Rectangle(width=10, height=20) |
解构赋值
解构赋值表达式是一种快速将数组和对象中的值解包到不同变量中的方法。
// Rhino runtime var data = {a: 12, b: false, c: 'blue'}; var a = data.a; var c = data.c; console.log(a, c); // Outputs 12 "blue" var a = [1, 2, 3]; var x = a[0]; var y = a[1]; var z = a[2]; console.log(x, y, z); // Outputs 1 2 3 |
// V8 runtime const data = {a: 12, b: false, c: 'blue'}; const {a, c} = data; console.log(a, c); // Outputs 12 "blue" const array = [1, 2, 3]; const [x, y, z] = array; console.log(x, y, z); // Outputs 1 2 3 |
模板字面量
模板字面量是一种允许嵌入表达式的字符串字面量。它们可让您避免使用更复杂的字符串串联语句。
// Rhino runtime var name = 'Hi ' + first + ' ' + last + '.'; var url = 'http://localhost:3000/api/messages/' + id; |
// V8 runtime const name = `Hi ${first} ${last}.`; const url = `http://localhost:3000/api/messages/${id}`; |
默认参数
借助默认形参,您可以在函数声明中为函数形参指定默认值。这可以简化函数正文中的代码,因为无需再为缺失的形参显式分配默认值。
// Rhino runtime function hello(greeting, name) { greeting = greeting || "hello"; name = name || "world"; console.log( greeting + " " + name + "!"); } hello(); // Outputs "hello world!" |
// V8 runtime const hello = function(greeting="hello", name="world") { console.log( greeting + " " + name + "!"); } hello(); // Outputs "hello world!" |
多行字符串
使用与模板字面量相同的语法定义多行字符串。与模板字面量一样,此语法可让您避免字符串串联并简化字符串定义。
// Rhino runtime var multiline = "This string is sort of\n" + "like a multi-line string,\n" + "but it's not really one."; |
// V8 runtime const multiline = `This on the other hand, actually is a multi-line string, thanks to JavaScript ES6`; |
V8 运行时限制
Apps 脚本 V8 运行时环境不是标准的 Node.js 或浏览器环境。当您调用第三方库或改编其他 JavaScript 环境中的代码示例时,这可能会导致兼容性问题。
不可用的 API
以下标准 JavaScript API 在 Apps 脚本 V8 运行时中不可用:
- 计时器:
setTimeout、setInterval、clearTimeout、clearInterval - 流:
ReadableStream、WritableStream、TextEncoder、TextDecoder - Web API:
fetch、FormData、File、Blob、URL、URLSearchParams、DOMException、atob、btoa - 加密货币:
crypto、SubtleCrypto - 全局对象:
window、navigator、performance、process(Node.js)
请改用以下 Apps 脚本 API:
- 计时器:使用
Utilities.sleep进行同步暂停。不支持异步计时器。 - 提取:使用
UrlFetchApp.fetch(url, params)发出 HTTP(S) 请求。 - atob:使用
Utilities.base64Decode解码 Base64 编码的字符串。 - btoa:使用
Utilities.base64Encode对字符串进行 Base64 编码。 - 加密:使用
Utilities实现加密功能,例如computeDigest、computeHmacSha256Signature和computeRsaSha256Signature。
对于没有 Apps 脚本替代方案的 API(例如 TextEncoder),您有时可以使用填充区。polyfill 是一种库,用于复制运行时环境中默认不可用的 API 功能。在使用填充区之前,请确认它与 Apps 脚本的 V8 运行时兼容。
异步限制
V8 运行时支持 async 和 await 语法以及 Promise 对象。不过,Apps 脚本运行时环境从根本上来说是同步的。
- 微任务(受支持):运行时会在当前调用堆栈清空后处理微任务队列(其中会发生
Promise.then回调和await解析)。 - 宏任务(不支持):Apps 脚本没有用于宏任务的标准事件循环。无法使用
setTimeout和setInterval等函数。 - WebAssembly 异常:WebAssembly API 是唯一在运行时以非阻塞方式运行的内置功能,可实现特定的异步编译模式 (WebAssembly.instantiate)。
所有 I/O 操作(例如 UrlFetchApp.fetch)都是阻塞操作。如需实现并行网络请求,请使用 UrlFetchApp.fetchAll。
课程限制
V8 运行时在现代 ES6+ 类功能方面存在特定限制:
- 私有字段:不支持私有类字段(例如
#field),会导致解析错误。考虑使用闭包或WeakMap实现真正的封装。 - 静态字段:不支持类正文中的直接静态字段声明(例如
static count = 0;)。在类定义后为其分配静态属性(例如MyClass.count = 0;)。
模块限制
- ES6 模块:V8 运行时不支持 ES6 模块 (
import/export)。如需使用库,您必须使用 Apps 脚本库机制,或者将代码及其依赖项捆绑到单个脚本文件中。(问题跟踪器) - 文件执行顺序:项目中的所有脚本文件都在全局范围内执行。最好避免使用具有副作用的顶级代码,并确保在跨文件使用函数和类之前先定义它们。如果文件之间存在依赖关系,请在编辑器中明确指定文件的顺序。
启用 V8 运行时
如果脚本使用的是 Rhino 运行时,请执行以下操作将其切换为 V8:
- 打开 Apps 脚本项目。
- 点击左侧的项目设置图标 。
- 选中启用 Chrome V8 运行时复选框。
或者,通过修改脚本清单文件直接指定脚本运行时:
- 打开 Apps 脚本项目。
- 点击左侧的项目设置图标 。
- 选中在编辑器中显示“appsscript.json”清单文件复选框。
- 在左侧,依次点击编辑器 >
appsscript.json。 - 在
appsscript.json清单文件中,将runtimeVersion字段的值设置为V8。 - 点击顶部的保存项目图标 。
将脚本迁移到 V8 一文介绍了您应采取的其他步骤,以确保脚本在 V8 中正常运行。
启用 Rhino 运行时
如果您的脚本使用的是 V8,并且您需要将其切换为使用原始 Rhino 运行时,请执行以下操作:
- 打开 Apps 脚本项目。
- 点击左侧的项目设置 。
- 清除启用 Chrome V8 运行时复选框。
或者,修改脚本清单:
- 打开 Apps 脚本项目。
- 点击左侧的项目设置 。
- 选中在编辑器中显示“appsscript.json”清单文件复选框。
- 在左侧,依次点击编辑器 >
appsscript.json。 - 在
appsscript.json清单文件中,将runtimeVersion字段的值设置为DEPRECATED_ES5。 - 点击顶部的保存项目图标 。
如何迁移现有脚本?
将脚本迁移到 V8 指南介绍了将现有脚本迁移到 V8 所需的步骤。这包括启用 V8 运行时并检查脚本是否存在任何已知的不兼容问题。
自动将脚本迁移到 V8
自 2020 年 2 月 18 日起,Google 会逐步将通过自动兼容性测试的现有脚本迁移到 V8。迁移后,受影响的脚本会继续正常运行。
如果您想让某个脚本不参与自动迁移,请将其清单中的 runtimeVersion 字段设置为 DEPRECATED_ES5。之后,您可以随时选择手动将脚本迁移到 V8。
如何报告 bug?
支持指南介绍了如何在 Stack Overflow 上获取编程帮助、搜索现有问题报告、提交新 bug 和提出新功能请求。