我正在尝试让Google Closure Compiler在作为设置或数据传递给函数时不重命名对象。通过查看jQuery中的注释,我认为这样可行:
/** @param {Object.<string,*>} data */
window.hello = function(data) {
alert(data.hello);
};
hello({ hello: "World" });
然而,它最终会像这样:
window.a = function(b) {
alert(b.a)
};
hello({a:"World"});
找到的ajax
函数here具有此注释,似乎可以正常工作。那么,为什么不呢?如果数据是来自外部源或设置对象的返回值,我希望能够告诉编译器不要触摸它,在我看来,使用this["escape"]
技巧会侵入这样的东西。< / p>
function ajax(success) {
// do AJAX call
$.ajax({ success: success });
}
ajax(function(data) {
alert(data.Success);
});
输出:
$.b({c:function(a){alert(a.a)}});
success
已重命名为c
,Success
(带有大写字母S)已重命名为a
。
我现在使用jQuery 1.6 externs file编译相同的代码并获得以下输出:
$.ajax({success:function(a){alert(a.a)}});
它还会产生警告,指出属性Success
未定义,正如我所料,但它无法将Success
重命名为a
,这仍然会破坏我的代码。我查看了ajax
的注释,我找到了这个类型表达式{Object.<string,*>=}
,我相应地注释了我的代码,然后重新编译。仍然没有工作......
答案 0 :(得分:8)
由于你的注意力集中在源头而不是输出上,所以你所关注的似乎是DRY(不要重复自己)。这是另一种DRY解决方案。
您可以使用--create_name_map_files
运行Closure Compiler。这样做会发出名为_props_map.out
的文件。你可以让你的JSON发射服务器端调用(ASP.Net MVC或它可能是什么)在发出它们的JSON时使用这些映射,因此它们实际上发出缩小的JSON,利用重命名Closure Compiler执行。这样,您可以在Controller和脚本上更改变量或属性的名称,添加更多等,并且缩小从脚本一直传回Controller控制器输出。您的所有来源(包括控制器)仍然是非缩小且易于阅读的。
答案 1 :(得分:6)
我认为你真正要做的是阻止它重命名从服务器上的AJAX控制器返回的对象上的属性名称,这显然会打破这个调用。
所以当你打电话
$.ajax({
data: { joe: 'hello' },
success: function(r) {
alert(r.Message);
}
});
你想让它单独留言,对吗?
如果是这样,你通过前面提到的方式完成,但它在输出中很好地编译为.Message。以上变为:
var data = {};
data['joe'] = 'hello';
$.ajax({
data: data,
/**
@param Object.<string> r
*/
success: function (r) {
alert(r['Message']);
}
});
现在缩小为:
$.ajax({data:{joe:"hello"},success:function(a){alert(a.Message)}});
使用r['Message']
代替r.Message
,可以防止缩小器重命名属性。这就是所谓的导出方法,正如您在Closure Compiler文档中所注意到的那样,它比externs更受欢迎。也就是说,如果你使用externs方法来做这件事,那么你就会让Google的几个人生气。他们甚至在名为“no”的标题上放了一个ID:
http://code.google.com/closure/compiler/docs/api-tutorial3.html#no
也就是说,你也可以使用externs方法做到这一点,这里有一点奇怪:
externs.js
/** @constructor */
function Server() { };
/** @type {string} */
Server.prototype.Message;
test.js
$.ajax({
data: { joe: 'hello' },
/**
@param {Server} r
*/
success: function (r) {
alert(r.Message);
}
});
C:\ java \ closure&gt; java -jar compiler.jar --externs externs.js --js jquery-1.6.js --js test.js --compilation_level ADVANCED_OPTIMIZATIONS --js_output_file output.js
出来了:
$.ajax({data:{a:"hello"},success:function(a){alert(a.Message)}});
答案 2 :(得分:3)
不幸的是,在所有地方进行data["hello"]
是阻止变量重命名的推荐(和官方)Closure方法。
我完全同意你的意见,我不喜欢这一点。但是,所有其他解决方案都会在编译时为您提供次优结果,或者可能会在不明显的情况下中断 - 如果您愿意接受次优结果,那么为什么要首先使用Closure Compiler?
但是,从服务器返回的数据实际上只需要处理,因为您应该能够安全地允许Closure重命名程序中的所有其他内容。随着时间的推移,我发现最好编写包装器来克隆从服务器返回的数据。换句话说:
var data1 = { hello:data["hello"] };
// Then use data1.hello anywhere else in your program
这样,任何未编译的对象只能在从Ajax反序列化后立即生存。然后它被克隆到一个可以由Closure编译/优化的对象中。在程序中使用此克隆的所有内容,您将获得Closure优化的全部好处。
我还发现,让这样一个“处理”函数立即处理从服务器通过Ajax传输的所有东西是有用的 - 除了克隆对象之外,你可以将后处理代码放在那里,如以及验证,错误更正和安全检查等。在许多Web应用程序中,您已经有了这样的功能来首先检查返回的数据 - 您从不信任从服务器返回的数据,你呢?
答案 3 :(得分:1)
游戏有点晚了,但我只是编写了一对处理所有入站和出站ajax对象的网关函数来解决这个问题:
//This is a dict containing all of the attributes that we might see in remote
//responses that we use by name in code. Due to the way closure works, this
//is how it has to be.
var closureToRemote = {
status: 'status', payload: 'payload', bit1: 'bit1', ...
};
var closureToLocal = {};
for (var i in closureToRemote) {
closureToLocal[closureToRemote[i]] = i;
}
function _closureTranslate(mapping, data) {
//Creates a new version of data, which is recursively mapped to work with
//closure.
//mapping is one of closureToRemote or closureToLocal
var ndata;
if (data === null || data === undefined) {
//Special handling for null since it is technically an object, and we
//throw in undefined since they're related
ndata = data;
}
else if ($.isArray(data)) {
ndata = []
for (var i = 0, m = data.length; i < m; i++) {
ndata.push(_closureTranslate(mapping, data[i]));
}
}
else if (typeof data === 'object') {
ndata = {};
for (var i in data) {
ndata[mapping[i] || i] = _closureTranslate(mapping, data[i]);
}
}
else {
ndata = data;
}
return ndata;
}
function closureizeData(data) {
return _closureTranslate(closureToLocal, data);
}
function declosureizeData(data) {
return _closureTranslate(closureToRemote, data);
}
这里的方便之处在于closureToRemote dict是平的 - 即使你必须指定子属性的名称以便闭包编译器知道,你可以在同一级别指定它们。这意味着响应格式实际上可能是一个相当复杂的层次结构,它只是您将直接通过名称访问的基本密钥,需要在某处进行硬编码。
每当我要进行ajax调用时,我都会通过declosureizeData()传递我发送的数据,这意味着我将数据从闭包的命名空间中取出。当我收到数据时,我要做的第一件事就是通过closureizeData()运行它来将名称输入到闭包的名称空间。
请注意,映射字典只需要在我们的代码中占一个位置,如果你有一个结构良好的ajax代码,它总是进出相同的代码路径,那么集成它就是“do-it-曾经忘掉它的“活动类型。
答案 4 :(得分:0)
您可以尝试将其定义为记录类型
/**
@param {{hello: string}} data
*/
告诉它数据具有string类型的属性hello。
答案 5 :(得分:0)
显然注释不应该归咎于此,只需在设置对象中引入一些未使用的属性就会导致编译器重命名。
我想知道它们来自何处以及我迄今为止唯一的逻辑解释(已确认here),是编译器保留一个不会重命名的事物的全局名称表。只需拥有一个带名字的extern就会导致该名称的任何属性都被保留下来。
/** @type {Object.<string,*>} */
var t = window["t"] = {
transform: function(m, e) {
e.transform = m;
},
skew: function(m, e) {
e.skew = m;
}
}
/**
* @constructor
*/
function b() {
this.transform = [];
this.elementThing = document.createElement("DIV");
}
t.transform(new b().transform, new b().elementThing);
结果如下:
function c() {
this.transform = [];
this.a = document.createElement("DIV")
}(window.t = {
transform: function (a, b) {
b.transform = a
},
b: function (a, b) {
b.b = a
}
}).transform((new c).transform, (new c).a);
请注意transform
未重命名但elementThing
是如何,即使我尝试注释此类型,也无法相应地重命名transform
。
但是,如果我添加以下外部源function a() {}; a.prototype.elementThing = function() {};
,尽管查看代码,它也不会重命名elementThing
,我可以清楚地告诉构造函数返回的类型与extern {{无关} 1}},但不知何故,这就是编译器的工作方式。我想这只是闭包编译器的一个限制,我认为这是一种耻辱。