为什么Closure Compiler坚持添加更多字节?

时间:2012-04-18 13:28:56

标签: javascript google-closure-compiler minify

如果我给Closure Compiler这样的话:

window.array = '0123456789'.split('');

它“编译”它:

window.array="0,1,2,3,4,5,6,7,8,9".split(",");

现在你可以说,这更大了。 Closure Compiler是否有任何理由这样做?

3 个答案:

答案 0 :(得分:21)

认为这是正在发生的事情,但我不确定......

导致插入逗号的代码是PeepholeSubstituteAlternateSyntax.java中的tryMinimizeStringArrayLiteral

该方法包含可能具有低Huffman encoding的字符列表,因此比其他字符更适合拆分。如果你尝试这样的话,你可以看到这个结果:

"a b c d e f g".split(" "); //Uncompiled, split on spaces
"a,b,c,d,e,f,g".split(","); //Compiled, split on commas (same size)

编译器会将您尝试拆分的字符替换为它认为有利的字符。它是通过迭代字符串的字符并找到字符串中没有出现的最有利的分裂字符来实现的:

// These delimiters are chars that appears a lot in the program therefore
// probably have a small Huffman encoding.
NEXT_DELIMITER: for (char delimiter : new char[]{',', ' ', ';', '{', '}'}) {
  for (String cur : strings) {
    if (cur.indexOf(delimiter) != -1) {
      continue NEXT_DELIMITER;
    }
  }
  String template = Joiner.on(delimiter).join(strings);
  //...
}

在上面的代码片段中,您可以看到编译器声称最适合拆分的字符数组。逗号是第一个(这就是为什么在上面的空间示例中,空格已被逗号替换)。

我认为在要拆分的字符串是空字符串的情况下插入逗号可能只是一种疏忽。对于这种情况似乎没有任何特殊处理,所以它被视为任何其他split调用,并且每个字符都与上面代码段中显示的数组中的第一个相应字符连接。


编译器如何处理split方法的另一个例子:

"a,;b;c;d;e;f;g".split(";"); //Uncompiled, split on semi-colons
"a, b c d e f g".split(" "); //Compiled, split on spaces

这一次,由于原始字符串已经包含逗号(并且我们不想在逗号字符上拆分),因此无法从低霍夫曼编码的字符数组中选择逗号,因此下一个选择最佳选择(空间)。


<强>更新

在对此进行进一步研究之后,它绝对不是一个错误。这种行为实际上是设计的,在我看来,这是一个非常聪明的小优化,当你记住Closure编译器倾向于支持编译代码的速度超过大小。

上面我提到过霍夫曼编码几次。非常简单地解释的霍夫曼编码算法为出现在要编码的文本中的每个字符分配权重。权重取决于每个角色出现的频率。这些频率用于构建二叉树,其中最常见的字符位于根。这意味着最常见的字符可以更快地解码,因为它们更接近树的根。

由于霍夫曼算法是gzip使用的DEFLATE算法的很大一部分。因此,如果您的Web服务器配置为使用gzip,您的用户将从这种聪明的优化中受益。

答案 1 :(得分:5)

答案 2 :(得分:4)

具有讽刺意味的是,编译代码中的split与源中的split无关。考虑:

Source  : a = ["0","1","2","3","4","5"]
Compiled: a="0,1,2,3,4,5".split(",")

在这里,split只是表示长数组的一种方式(长度足以使所有引号+逗号的总和超过split(",""))。那么,你的例子中发生了什么?首先,编译器看到应用于常量的字符串函数并立即对其进行求值:

'0123456789'.split('') => ["0","1","2","3","4","5","6","7","8","9"]

稍后,在生成输出时,编译器会将此数组视为&#34; long&#34;并将其写在上面的&#34; split&#34;形式:

["0","1","2","3","4","5","6","7","8","9"] => "0,1,2,3,4,5,6,7,8,9".split(",")

请注意,此时源中split('')的所有信息都已丢失。

如果源字符串较短,它将以数组数组的形式生成,无需额外拆分:

Source  : a = '0123'.split('')
Compiled: a=["0","1","2","3"]