Summary:
Google Apps 脚本运行在stateless环境。存储在全局对象中的任何内容都不会跨会话维护。如果您在会话期间向全局对象添加某些内容,则该内容在下一次会话运行中不可用。使用立即调用函数或全局范围内的调用函数来填充全局范围(this
对象)在用户界面实际调用任何函数之前。
解释:
提到的解决方法[电子邮件受保护] in 本期评论#17以及田池在这个答案两者都利用闭包/立即调用函数(IIFE)填充全局范围。
要理解这一点,您需要了解何时读取和加载脚本函数名称。以下步骤按顺序发生:
-
菜单点击(按钮点击似乎跳过了步骤1和2,因此似乎不可拦截)
-
脚本编辑器中的所有脚本均被执行,并且全局函数名称列表this
创建(在此步骤中,没有运行/调用任何函数,但所有脚本都已完全执行)。这相当于使用脚本加载网页:<script>...code.gs...</script>
-
检查当前调用的按钮/菜单的功能名称是否存在于全局中this
,
-
如果存在,则执行该函数(即调用链接按钮/菜单的函数名称引用的函数)。这就像添加myFunction()
在已加载脚本的末尾。如果没有找到,则抛出错误:Script function not found
-
脚本结束。这就像关闭加载的网页一样。所有“状态”都丢失了。没有全局范围或this
被永久保存。
使用动态添加菜单项时this[function-name]
,在添加该功能时意识到这一点很重要。如果您在期间添加它onOpen
, then this
在全局范围内具有这些功能onOpen
执行,但之后立即丢失onOpen
脚本执行完成。
function onOpen(){
this['a'] = () => 'a';
SpreadsheetApp.getUi()
.createMenu("Test")
.addItem("Call function a","a")
.addToUi()
}
这样就可以成功添加了a
功能为Ui
菜单,但请注意a
仅添加到全局this
scope during onOpen
执行。这this
然后在执行完成后丢失并且新的this
(全局范围)在下次调用任何函数时创建(重复步骤 1 到 5)。因此,当单击菜单时,步骤 2 会创建一个新的this
并寻找一个名为的函数a
在所有脚本中,但找不到任何脚本,因为这是新创建的this
没有a
(因为onOpen
已声明,但未执行,因此a
没有添加到this
这次)。
解决方案:
在步骤 2 期间或之前,您需要将该函数添加到全局this
:
function onOpen(){
SpreadsheetApp.getUi()
.createMenu("Test")
.addItem("Call function a","a")
.addToUi()
}
(function IIFE(){
this['a'] = () => 'a';
})();
上面的 IIFE 函数拦截了步骤 2“每次”,调用任何函数。所以a
总是存在于this
在步骤 3 时或之后。田池的解决方案,这是由installFunctions()
在全球范围内。每次调用任何函数时都会执行该函数。同样的情况也适用于createMenuFunctions(this);
in 评论#17.
文档摘录:
From 附加文档链接,
警告:当 onOpen(e) 函数运行时,将加载整个脚本并执行所有全局语句。这些语句在与 onOpen(e) 相同的授权模式下执行,如果模式禁止它们,则会失败。这会阻止 onOpen(e) 运行。如果您发布的附加组件无法添加其菜单项,请查看浏览器的 JavaScript 控制台以查看是否引发了错误,然后检查您的脚本以查看 onOpen(e) 函数或全局变量是否调用了不允许的服务在 AuthMode.NONE 中。
示例脚本:
/**Runs every time any script function is called*/
(function IIFE(scope) {
'use strict';
scope['options'] = ['a', 'b', 'c']; //pollute current scope
options.forEach(
option =>
(scope[option] = () =>
SpreadsheetApp.getUi().alert(`You clicked option ${option}`))
);
})(this);//pass global scope
function onOpen() {
const testMenu = SpreadsheetApp.getUi().createMenu('Test');
options.forEach(option =>
testMenu.addItem('Call function ' + option, option)
);
testMenu.addToUi();
}
参考: