繁体   English   中英

防止Google Closure Compiler重命名设置对象

[英]Prevent Google Closure Compiler from renaming settings objects

我试图让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函数有这个注释,它似乎工作。 那么,为什么不呢? 如果数据是来自外部源或设置对象的返回值,我希望能够告诉编译器不要触摸它,使用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文件编译相同的代码并获得以下输出:

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

它还会产生一个警告,表明属性Success未定义,正如我所料,但它不能将Success重命名为a ,这仍然会破坏我的代码。 我查看了ajax的注释,我找到了这个类型表达式{Object.<string,*>=} ,我相应地注释了我的代码,然后重新编译。 还是行不通...

既然你的注意力集中在源头而不是输出上,那么你所关注的似乎就是DRY(不要重复自己)。 这是另一种DRY解决方案。

您可以使用--create_name_map_files运行Closure Compiler。 这样做会发出一个名为_props_map.out的文件。 你可以让你的JSON发射服务器端调用(ASP.Net MVC或它可能是什么)在发出它们的JSON时使用这些映射,因此它们实际上发出缩小的JSON,利用重命名Closure Compiler执行。 这样,您可以在Controller和脚本上更改变量或属性的名称,添加更多等,并且缩小从脚本一直传回Controller控制器输出。 您的所有源代码(包括Controller)仍然是非缩小且易于阅读的。

我认为你真正要做的就是阻止它重命名从服务器上的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 ,可以防止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> 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)}});

不幸的是,在整个地方做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应用程序中,您已经拥有了这样的功能,可以首先检查返回的数据 - 您永远不会信任从服务器返回的数据,现在对吗?

游戏稍晚,但我只是通过编写一对处理所有入站和出站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-once-and - 忘记它“活动的类型。

您可以尝试将其定义为记录类型,

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

这告诉它数据具有string类型的属性hello。

显然注释不应该归咎于此,仅仅通过向设置对象引入一些未使用的属性将导致编译器重命名内容。

我想知道这些来自何处以及我迄今为止唯一合乎逻辑的解释( 此处已确认),是编译器保留一个不会重命名的事物的全局名称表。 只需拥有一个带名字的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

但是如果我添加以下extern源function a() {}; a.prototype.elementThing = function() {}; function a() {}; a.prototype.elementThing = function() {}; 尽管查看代码,它也不会重命名elementThing ,我可以清楚地告诉构造函数返回的类型与extern a无关,但不知怎的,编译器就是这样做的。 我想这只是闭包编译器的一个限制,我认为这是一种耻辱。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM