对于Javascript来说,提高网络下载的性能最直接的方法就是把JS文件体积减小。
>>>留意亮点
为了方便理解和对比,本文会给出压缩前后代码作为参考,但压缩后的代码仍会换行,变量名字不做混淆处理,同时一个压缩规则的例子会尽量不混其它压缩策略进去。
将可预先计算的表达式替换成其计算结果,同时要比较原来表达式以及生成后的结果的大小,保留小的。
压缩前
var expr1 = 1 + 1; var expr2 = 1 / 3;
压缩后
var expr1 = 2; var expr2 = 1 / 3;//由于计算出来的值0.3333333333333比1/3要长,所以不预计算
正常情况下会把: true变成!0,节省2个字符;false变成!1,节省3个字符。这会让人会疑问:这里为什么不直接把true变成1,false变成0呢?因为这样会把一个布尔类型变成数字类型参与某些运算导致运行时混乱。 那么有没有什么情况比较特殊,可以把true变成1、false变成0呢?答案是有的:就是在参与==以及!=运算时。
压缩前
var expr1 = true; var expr2 = false; true == A; false == A;
压缩后
var expr1 = !0; var expr2 = !1; 1 == A; 0 == A;
压缩前
true && A(); false && A(); true || A(); false || A();
压缩后
A();//返回&&第二个操作数的值 !1;//返回&&第一个操作数的值 !0;//返回||第一个操作数的值 A();//返回&&第二个操作数的值
留意:这里typeof A得到的结果是string类型,b instanceof B得到的结果是布尔类型。
压缩前
"object" === typeof A; true !== b instanceof B;
压缩后
"object" == typeof A; true != b instanceof B;
这里说起来可能有点绕,但是想一下也很容易理解这条规则的详细判断:
压缩前
a = a + b; c = c >>> d; a = b + c;
压缩后
a += b; c >>>= d; a = b + c;
对a>=b取非可以得到a<b,对a&&b取非可以得到!a||!b。如果转换后的结果能得到更短的代码,那就将这个取非的表达式换成转换后的表达式。
压缩前
!(a>=b) !!!a
压缩后
a<b !a
对于嵌套的作用域使用了同样的指示性字符串,其实子作用域的是可以去除的。
压缩前
function A(){ "use strict"; function B(){ "use strict"; } }
压缩后
function A(){ "use strict"; function B(){ } }
参数c在函数A里边没有使用,所以直接去除c参数的声明。也许你会好奇为什么参数a也没有使用却不去掉,如果去掉参数a,就会改变了b所在的参数位置。
例如:调用A(1,2)时候,本来b应该是2的,如果去除参数a,这个时候b就会变成1,这样会引起错误。因此UglifyJS在去除函数参数的时候都是从后往前扫描变量在函数里边的引用。
压缩前
function A(a, b, c){ b ++; }
压缩后
function A(a, b){ b++; }
对于一个函数表达式,如果其函数体没有引用自身名字递归调用,那么这个函数名可以去除,使之变为匿名函数。
压缩前
(function A(){ A(); })(); (function B(){ c++; })();
压缩后
(function A(){ A(); })(); (function(){ c++; })();
如果块里边没有语句或者只有一条语句,那么这个块可以去掉{ }。
压缩前
while (f){ { A(); } } while (f){ { } } if (A){ B(); }
压缩后
while (f) A(); while (f) ; if (A) B();
switch最后一个case/default分支块的最后一个语句如果是“break;”的话,可以忽略,如果break后边带有标签则不能去除。
压缩前
switch(A){ case 1: break; case 2: break; } label: switch(A){ case 1: break; case 2: break label; }
压缩后
switch(A){ case 1: break; case 2://这里最后的break;可以忽略 } label: switch(A){ case 1: break; case 2: break label;//带标签的break不可以忽略 }
压缩前
label1:var a = 1; label2:while (true){ break label2; }
压缩后
var a = 1; label2:while (true){ break label2;//label2被引用 不可以去掉 }
这个规则在某些条件可能会有不安全的问题产生,因此UglifyJS只有在你明确调用时带上–unsafe的参数才会做这个压缩。
压缩前
(typeof A).toString(); ("A" + "B").toString(); var expr = "str".toString();
压缩后
(typeof A); "AB"; var expr = "str";
压缩前
while(false){ A(); B(); } while(true){ C(); D(); }
压缩后
//while(false)被压缩忽略掉 for(;;){ //while(true)转换成for(;;)节省4个字符 C(); D(); }
条件表达式使用了三元运算符 ? :,例如:cond ? yes() : no()
压缩前
!cond ? yes() : no();
压缩后
cond ? no() : yes();
压缩前
true ? yes() : no(); false ? yes() : no();
压缩后
yes(); no();
函数体、with都会生成一个语句块,下边规则是针对语句块的压缩优化。
>>>留意:这里要表达式语句才可以。
压缩前
function A(){ B(); C(); d = 1; }
压缩后
function A(){ B(), C(), d = 1; }
压缩前
function A(){ var a; var b; var c; }
压缩后
function A(){ var a, b, c; }
在块里边return之后的语句是不会被执行到的,所以可以被去除。但是Javascript中在块里边无论什么地方声明都可以(声明会被提升),因此return之后的声明是不能去掉的。 当然这里不仅仅是return之后的语句可以去除,还有throw、break、continue之后的语句也适用于这条规则。
压缩前
function A(){ return false; var a = 1; function B(){ } expr += 1; a = 3; }
压缩后
function A(){ function B(){ } return false; var a = 1; }
其实这条规则看起来并不会使最后生成的代码缩小。
合并前
function A(){ B(); C(); return D(); }
合并后
function A(){ return B(), C(), D(); }
接下来开始复杂丰富多彩的IF分支压缩!
如果if的条件是可预计算得到的常数结果,那么就可以忽略掉没用的if/else分支。
压缩前
if (true){ A(); }else{ B(); } if (false){ C(); }else{ D(); }
压缩后
A(); D();
如果是if分支是空的话,把条件取非,else分支反转成if分支即可。
压缩前
if (A){ B(); }else{ } if (C){ }else{ D(); }
压缩后
if (A){ B(); } if (!C){ D(); }
尝试对if条件取非,如果能得到更短的代码,那就反转if/else分支。
压缩前
if (!c){ A(); }else{ B(); }
压缩后
if (c){ B(); }else{ A(); }
压缩前
if (A){ if (B){ C(); } }else{ }
压缩后
if (A && B){ C(); }
压缩前
if (A){ B(); return; }else{ C(); }
压缩后
if (A){ B(); return; } C();
压缩前
if (A){ return B(); }else{ return C(); }
压缩后
return A ? B() : C();
即把适合的if语句转化为三目条件表达式,具体请参考规则5.1与5.2。
压缩前
if (A){ B(); }else{ } if (C){ }else{ D(); }
压缩后
A && B(); C || D();
更详细的源码分析可点击下载查看本文作者对UglifyJS源码的注释。源码注释下载