2011-10-19 30 views
13

我试图让Google Closure编译器在作为设置或数据传递给函数时不重命名对象。通过查看目前在jQuery的注释,我想这会工作:防止Google Closure编译器重命名设置对象

/** @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"]技巧就是在我看来这样干扰这种事情。

这里有一个更好的例子

function ajax(success) { 
     // do AJAX call 
    $.ajax({ success: success }); 
} 
ajax(function(data) { 
    alert(data.Success); 
}); 

输出:

$.b({c:function(a){alert(a.a)}}); 

success已更名为cSuccess(大写S)已更名为a

我现在编译与jQuery 1.6 externs file相同的代码,并得到下面的输出:

$.ajax({success:function(a){alert(a.a)}}); 

它也产生了财产Success没有定义一个警告,因为我所期望的,但不能重命名Success到只需a,这仍然会破坏我的代码。我查看ajax的注释,我发现这种类型的表达{Object.<string,*>=},我相应地注释了我的代码,然后重新编译。仍然没有工作...

+1

为了更好地理解以后任何人的阅读:链接的JS是一个externs文件。它仅与代码编译一起使用,以防止重命名“外部化”变量,属性和函数/方法。其中的注释只是表示编译时类型检查的正确使用。他们绝不会指示编译器不重命名jQuery的方法和参数。 – Kiruse

回答

8

因为你的焦点似乎是源,而不是输出上,好像你专注于干什么(唐不要重复自己)。这是另一种DRY解决方案。

您可以使用--create_name_map_files运行Closure编译器。这样做会发出一个名为_props_map.out的文件。你可以让JSON发出的服务器端调用(ASP.Net MVC或任何可能的)在发布他们的JSON时使用这些映射,所以他们实际上发出了缩小的JSON,它利用重命名Closure Compiler执行的操作。通过这种方式,您可以更改控制器和脚本上的变量或属性的名称,添加更多等等,并且缩小从脚本一直返回到Controller输出。包括Controller在内的所有源代码都将继续保持非缩小状态,并且易于阅读。

+0

这是我愿意与之合作的解决方案。有一些工作要做,但这是一个非常好的主意。 –

+0

这个'_props_map.out'文件究竟是什么?我已经构建了一个,但我没有对我做出舔... –

+1

NVM,我甚至没有打开高级优化运行编译器。 –

0

你可以尝试将其定义为一个记录类型,

/** 
    @param {{hello: string}} data 
*/ 

,告诉它的数据具有字符串类型的物业打招呼。

+0

即使这样做,我没有一个静态记录,它是一个具有很多属性,设置和数据的对象。我不知道模式,所以我不能定义它。我的工作理论是封闭编译器应该从类型注释中得到它,但它看起来并没有工作。 –

0

显然注释不是在这里责怪,只是通过引入一些未使用的属性设置对象将导致编译器重命名的东西。

我想知道这些来自哪里以及迄今唯一合乎逻辑的解释(确认here),是编译器保留一个不会重命名的全局名称表。只要有一个名字的外部名字就会导致该名字的任何属性被保留。

/** @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

但是,如果我添加下面的extern源function a() {}; a.prototype.elementThing = function() {};不会尽管看代码命名elementThing,我可以明确地告诉大家,由构造函数返回的类型是无关的extern a,但不知何故,这是怎样的编译器做到了。我想这只是闭包编译器的限制,我认为这是一个令人不知所措的耻辱。

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']); 
    } 
}); 

Minifies现在:

$.ajax({data:{joe:"hello"},success:function(a){alert(a.Message)}}); 

通过使用r['Message']代替r.Message,可以防止由minifier属性重命名。这就是所谓的export方法,正如你在Closure Compiler文档中会注意到的那样,优先于externs。也就是说,如果你使用externs方法来做到这一点,你会让几个人在谷歌生气。他们甚至把上命名为“无”的标题的ID: http://code.google.com/closure/compiler/docs/api-tutorial3.html#no

这就是说,你也可以做到这一点使用方法实习医生,在这里它是在其所有的怪事:

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的\闭合> Java的罐子compiler.jar --externs externs.js --js的jquery-1.6.js --js test.js - -汇编_水平ADVANCED_OPTIMIZATIONS --js_output_file output.js

进出谈到:

$.ajax({data:{a:"hello"},success:function(a){alert(a.Message)}}); 
+0

所以正是我不想做的事情,并不是说这里的编译器会将'this [“abc”]'转换为'this.abc',因为它更短,但我不在乎在这种情况下输出,我所关心的是输入,因为这就是我创作的内容。而且我不想在任何地方都用括号编写代码,它太冗长了。但是,在我的数据模式中使用'externs.js'实际上是我最终可能做的事情,但它会让您远离使用动态对象的一些好处,这就是我在这里遇到的问题。 –

+0

我最终可能不会编译这些动态脚本,现在还不确定是否有更好的方法。 –

+0

如果你采用上面的Externs方法,你可以为每个(我假设的)发出JSON的MVC控制器添加一个条目,这实际上相当不错 - 你可以从控制器发出的数据开始获得类型安全。我会发布第二个答案,重点关注干(不要重复自己)。 –

3

不幸的是,在全国各地做data["hello"]是推荐(和官方)关闭方式来防止变量重命名。

我完全同意你的看法,我不喜欢这一点。但是,所有其他解决方案在编译时会给您带来次优的结果,或者可能会在不明确的情况下破解 - 如果您愿意接受次优结果,那么为什么首先使用Closure Compiler?

但是,从服务器返回的数据实际上是您需要处理的,因为您应该能够安全地允许Closure重命名程序中的其他所有内容。过了一段时间,我发现最好编写能够克隆从服务器返回的数据的包装器。换句话说:

var data1 = { hello:data["hello"] }; 
// Then use data1.hello anywhere else in your program 

这样,任何未加密的对象仅在从Ajax反序列化后才短暂存在。然后它被克隆到一个可以通过Closure进行编译/优化的对象中。在你的程序中使用这个克隆的所有东西,你将获得Closure优化的全部好处。

我也发现让这样一个“处理”函数立即处理来自服务器的所有来自Ajax的内容是非常有用的 - 除了克隆对象之外,还可以在其中放置后处理代码,以及验证,错误更正和安全检查等。在许多Web应用程序中,您已经具备了对返回数据进行此类检查的功能 - 您从现在的服务器返回的信任数据,您现在还不知道?

+0

我开始意识到试图避免“所有没有引用”的方法是徒劳的... –

+0

欢迎来到俱乐部......花了我一段时间才意识到这种徒劳......还有点让我想起了老黑客电影“战争游戏” - 一场奇怪的游戏,唯一的赢家举动就是不玩。关闭时,唯一的办法就是按照自己的方式去做,或者根本不使用它。 :-( –

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字典是平的 - 即,即使您必须指定子属性的名称以便闭包编译器知道,也可以在同一级别指定它们。这意味着响应格式实际上可以是一个相当复杂的层次结构,它只是您将通过名称直接访问的基本密钥,需要在某处进行硬编码。

每当我打算做一个ajax调用时,我都会传递我通过declosureizeData()发送的数据,这意味着我正在从关闭的命名空间中取出数据。当我接收到数据时,我所做的第一件事是通过closureizeData()来获取名称到闭包的命名空间中。

请注意,映射字典只需要在我们的代码中有一个位置,并且如果您的结构良好的ajax代码始终进出相同的代码路径,那么将它整合为“do-it一次又一次地忘记它“的活动类型。

相关问题