在Google extends the import/export API to container-bound Apps Script projects之前,我已将我的大部分项目移至可以使用该API的库,然后将Google Docs项目转换为只调用库的shell。
我的问题是让图书馆访问与Google Doc项目相同的属性(PropertiesService
)。由于我有Docs Add-on的现有用户,因此我需要继续使用这些属性。
在我的Google文档项目中,我尝试了
$.PropertiesService = PropertiesService;
(其中$
是我的图书馆。)
它不起作用。图书馆继续使用自己的属性。
然后我尝试了。
function _mock(obj) {
var ret = {};
for(var key in obj) {
if(typeof obj[key] == 'function') {
ret[key] = obj[key].bind(obj);
} else {
ret[key] = obj[key];
}
}
return ret;
}
$.PropertiesService = _mock(PropertiesService);
仍然无法正常工作。再试一次:
function _mock(obj) {
var ret = {};
for(var key in obj) {
if(typeof obj[key] == 'function') {
ret[key] = (function(val) {
return function() {
return val.apply(obj, arguments);
};
})(obj[key]);
} else {
ret[key] = obj[key];
}
}
return ret;
}
$.PropertiesService = _mock(PropertiesService);
这很有效。
此时,我想知道:
为什么前两种方法不起作用,但第三种方式呢?
我可以期待这种情况继续发挥作用吗?
有没有更好的方法让库访问主脚本的属性?
文档很少。有this,但未提及PropertiesService
。
答案 0 :(得分:2)
共享资源
您知道,libraries具有共享和非共享资源。 PropertiesService
列在非共享资源下,这意味着该库具有它自己的服务的实例,当您在中引用该资源时可以访问该服务。库代码。
const getStore = () => PropertiesService.getScriptProperties();
如果上面的函数在库中声明,则将使用库的资源(如果在调用脚本中)-它自己的实例。
V8运行时解决方案
V8运行时does not create a special context用于代码,使您可以直接访问内置服务。因此,在使用运行时时,可以通过简单地在全局this
上定义或替换属性来注入服务:
//in the library;
var getProperty = ((ctxt) => (key) => {
var service = ctxt.injectedService;
var store = service.getScriptProperties();
return store.getProperty(key);
})(this);
var setProperty = ((ctxt) => (key, val) => {
var service = ctxt.injectedService;
var store = service.getScriptProperties();
return store.setProperty(key, val);
})(this);
var inject = ((ctxt) => (service) => ctxt.injectedService = service)(this);
var greet = ((ctxt) => () => {
var store = ctxt.injectedService.getScriptProperties();
return store.getProperty("greeting") || "Ola!";
})(this);
//in the calling script;
function testSharedResources() {
PropertiesService.getScriptProperties().setProperty("greeting", "Hello, lib!");
$.inject(PropertiesService);
Logger.log($.greet()); //Hello, lib!
$.setProperty("greeting", "Hello, world!");
Logger.log($.greet()); //Hello, world!
}
在某些情况下,全局this
将是undefined
(将库添加到绑定脚本时遇到了此问题)。在这种情况下,只需定义一个私有全局名称空间(以避免泄露给调用者脚本):
//in the library;
var Dependencies_ = {
properties : PropertiesService
};
var use = (service) => {
if ("getScriptProperties" in service) {
Dependencies_.properties = service;
}
};
//in the calling script;
$.use(PropertiesService);
Rhino运行时解决方案
另一方面,较旧的Rhino运行时会创建一个特殊的隐式上下文。这意味着您无权访问内置服务或全局this
。您唯一的选择是绕过库中的服务调用(您的方法3最适合这样做)。
问题
与您的方法有关的所有问题归结为:
但是有一个陷阱:所有三种方法都能按设计工作。
首先,如果您专门引用PropertiesService
上的$
,则方法一个有效。这是有道理的,因为库是作为名称空间包含的,其成员映射到库中的全局声明。例如:
//in the caller script
PropertiesService.getScriptProperties().setProperty("test", "test");
$.PropertiesService = PropertiesService;
Logger.log( $.PropertiesService.getScriptProperties().getProperty("test") ); // "test"
Logger.log( $.getProperty("test") ); // "null"
//in the library
function getProperty(key) {
var store = PropertiesService.getScriptProperties();
return store.getProperty(key);
}
处理两个也有效。如果在库中被调用 ,则调用者脚本中函数的绑定不会改变事实,它会接收到库上下文,但是如果您直接在调用脚本中调用绑定副本,它将起作用:
//in the caller script
PropertiesService.getScriptProperties().setProperty("test", "test");
var bound = $.PropertiesService.getScriptProperties.bind(PropertiesService);
var obj = { getScriptProperties : bound };
$.PropertiesService = obj;
Logger.log( bound().getProperty("test") ); // "test"
Logger.log( $.getProperty("test") ); // "null"
现在,为什么第三种方法开箱即用?由于包装函数捕获调用脚本{em> 的PropertiesService
并应用getScriptProperties
方法而导致的关闭。为了说明:
//in the caller script
var appl = {
getScriptProperties : (function(val) {
return function() {
return val.apply(PropertiesService);
};
})(PropertiesService.getScriptProperties)
};
$.PropertiesService = appl;
Logger.log( $.getProperty("test") ); // "test"
是,不是。是的,因为您的_mock
函数行为在所有情况下都表现出预期的行为。否,因为apply
依赖于getScriptProperties
未被实现为箭头功能,其中this
覆盖将被忽略。
对于Rhino运行时-不要这样。对于V8-直接注入服务就足够了。