JavaScript代码压缩细节

前言

对于Javascript来说,提高网络下载的性能最直接的方法就是把JS文件体积减小。

为了方便理解和对比,本文会给出压缩前后代码作为参考,但压缩后的代码仍会换行,变量名字不做混淆处理,同时一个压缩规则的例子会尽量不混其它压缩策略进去。

1. 表达式的压缩

规则1.1 表达式预计算

将可预先计算的表达式替换成其计算结果,同时要比较原来表达式以及生成后的结果的大小,保留小的。

压缩前

var expr1 = 1 + 1; var expr2 = 1 / 3;

12

var expr1 = 1 + 1; var expr2 = 1 / 3;

压缩后

var expr1 = 2; var expr2 = 1 / 3;<span style="color: #999999;">//由于计算出来的值0.3333333333333比1/3要长,所以不预计算</span>

12

var expr1 = 2;var expr2 = 1 / 3;<span style="color: #999999;">//由于计算出来的值0.3333333333333比1/3要长,所以不预计算</span>

规则1.2 优化true跟false

正常情况下会把: true变成!0,节省2个字符;false变成!1,节省3个字符。这会让人会疑问:这里为什么不直接把true变成1,false变成0呢?因为这样会把一个布尔类型变成数字类型参与某些运算导致运行时混乱。 那么有没有什么情况比较特殊,可以把true变成1、false变成0呢?答案是有的:就是在参与==以及!=运算时。

压缩前

var expr1 = true; var expr2 = false; true == A; false == A;

1234

var expr1 = true;var expr2 = false;true == A;false == A;

压缩后

var expr1 = !0; var expr2 = !1; 1 == A; 0 == A;

1234

var expr1 = !0;var expr2 = !1;1 == A;0 == A;

规则1.3 根据&&与||短路的特性压缩表达式

压缩前

true && A(); false && A(); true || A(); false || A();

1234

true && A();false && A();true || A();false || A();

压缩后

A();<span style="color: #999999;">//返回&&第二个操作数的值 </span>!1;<span style="color: #999999;">//返回&&第一个操作数的值 </span>!0;<span style="color: #999999;">//返回||第一个操作数的值 </span>A();<span style="color: #999999;">//返回&&第二个操作数的值</span>

1234

A();<span style="color: #999999;">//返回&&第二个操作数的值</span>!1;<span style="color: #999999;">//返回&&第一个操作数的值</span>!0;<span style="color: #999999;">//返回||第一个操作数的值</span>A();<span style="color: #999999;">//返回&&第二个操作数的值</span>

2. 运算符缩短

留意:这里typeof A得到的结果是string类型,b instanceof B得到的结果是布尔类型。

压缩前

"object" === typeof A; true !== b instanceof B;

12

"object" === typeof A;true !== b instanceof B;

压缩后

"object" == typeof A; true != b instanceof B;

12

"object" == typeof A;true != b instanceof B;

规则2.2 缩短赋值表达式,对于a = a + b这样的赋值表达式,可以缩短成 a += b

这里说起来可能有点绕,但是想一下也很容易理解这条规则的详细判断:

  1. 必须是=号赋值语句;
  2. =号左侧只能是变量,不能为表达式等;
  3. =号右侧必须为二元操作表达式,并且符号是为数组[‘+’, ‘-‘, ‘/’, ‘*’, ‘%’, ‘>>’, ‘<<‘, ‘>>>’, ‘|’, ‘^’, ‘&’]中的元素;
  4. =号右侧的二元表达式的第一个操作数必须跟=号左侧的变量一致。

压缩前

a = a + b; c = c >>> d; a = b + c;

123

a = a + b;c = c >>> d;a = b + c;

压缩后

a += b; c >>>= d; a = b + c;

123

a += b;c >>>= d;a = b + c;

规则2.3 操作符非!的压缩

对a>=b取非可以得到a<b,对a&&b取非可以得到!a||!b。如果转换后的结果能得到更短的代码,那就将这个取非的表达式换成转换后的表达式。

压缩前

!(a>=b) !!!a

12

!(a>=b)!!!a

压缩后

a<b !a

12

a<b!a

3. 去除没用的声明/引用

规则3.1 去除重复的指示性字符串

对于嵌套的作用域使用了同样的指示性字符串,其实子作用域的是可以去除的。

压缩前

function A(){ "use strict"; function B(){ "use strict"; } }

123456

function A(){  "use strict";  function B(){    "use strict";  }}

压缩后

function A(){ "use strict"; function B(){ } }

12345

function A(){  "use strict";  function B(){  }}

规则3.2 去除没有使用的函数参数

参数c在函数A里边没有使用,所以直接去除c参数的声明。也许你会好奇为什么参数a也没有使用却不去掉,如果去掉参数a,就会改变了b所在的参数位置。

例如:调用A(1,2)时候,本来b应该是2的,如果去除参数a,这个时候b就会变成1,这样会引起错误。因此UglifyJS在去除函数参数的时候都是从后往前扫描变量在函数里边的引用。

压缩前

function A(a, b, c){ b ++; }

123

function A(a, b, c){   b ++;}

压缩后

function A(a, b){ b++; }

123

function A(a, b){   b++;}

规则3.3 去除函数表达式冗余的函数名

对于一个函数表达式,如果其函数体没有引用自身名字递归调用,那么这个函数名可以去除,使之变为匿名函数。

压缩前

(function A(){ A(); })(); (function B(){ c++; })();

123456

(function A(){  A();})();(function B(){  c++;})();

压缩后

(function A(){ A(); })(); (function(){ c++; })();

123456

(function A(){  A();})();(function(){  c++;})();

规则3.4 去除没用的块

如果块里边没有语句或者只有一条语句,那么这个块可以去掉{ }。

压缩前

while (f){ { A(); } } while (f){ { } } if (A){ B(); }

123

while (f){ { A(); } }while (f){ { } }if (A){ B(); }

压缩后

while (f) A(); while (f) ; if (A) B();

123

while (f) A();while (f) ;if (A) B();

规则3.5 去除没有使用的break;

switch最后一个case/default分支块的最后一个语句如果是“break;”的话,可以忽略,如果break后边带有标签则不能去除。

压缩前

switch(A){ case 1: break; case 2: break; } label: switch(A){ case 1: break; case 2: break label; }

12345678

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:<span style="color: #999999;">//这里最后的break;可以忽略</span>} label: switch(A){ case 1: break; case 2: break label;<span style="color: #999999;">//带标签的break不可以忽略</span>}

123456

switch(A){  case 1: break;  case 2:<span style="color: #999999;">//这里最后的break;可以忽略</span>}label: switch(A){  case 1: break;  case 2: break label;<span style="color: #999999;">//带标签的break不可以忽略</span>}

规则3.6 去除没有引用的label

压缩前

label1:var a = 1; label2:while (true){ break label2; }

1234

label1:var a = 1;label2:while (true){  break label2;}

压缩后

var a = 1; label2:while (true){ break label2;<span style="color: #999999;">//label2被引用 不可以去掉</span>}

123

var a = 1;label2:while (true){  break label2;<span style="color: #999999;">//label2被引用 不可以去掉</span>}

规则3.7 去除没作用的toString函数调用

这个规则在某些条件可能会有不安全的问题产生,因此UglifyJS只有在你明确调用时带上–unsafe的参数才会做这个压缩。

压缩前

(typeof A).toString(); ("A" + "B").toString(); var expr = "str".toString();

123

(typeof A).toString();("A" + "B").toString(); var expr = "str".toString();

压缩后

(typeof A); "AB"; var expr = "str";

123

(typeof A); "AB";var expr = "str";

4. while压缩

规则4.1 去除根本不会执行的while循环、将循环巧妙变化节省字符

压缩前

while(false){ A(); B(); } while(true){ C(); D(); }

12345678

while(false){   A();   B(); } while(true){   C();   D(); }

压缩后

<span style="color: #999999;">//while(false)被压缩忽略掉 </span>for(;;){ <span style="color: #999999;">//while(true)转换成for(;;)节省4个字符 </span> C(); D(); }

1234

<span style="color: #999999;">//while(false)被压缩忽略掉 </span>for(;;){ <span style="color: #999999;">//while(true)转换成for(;;)节省4个字符 </span>  C();   D(); }

5. 条件表达式

条件表达式使用了三元运算符 ? :,例如:cond ? yes() : no()

规则5.1 如果cond前边有非运算,那么考虑把非去掉,然后调转yes()跟no()的位置

压缩前

!cond ? yes() : no();

1

!cond ? yes() : no();

压缩后

cond ? no() : yes();

1

cond ? no() : yes();

规则5.2 如果cond是一个常数值或布尔值,那么可以直接缩短为yes()或者no()

压缩前

true ? yes() : no(); false ? yes() : no();

12

true ? yes() : no();false ? yes() : no();

压缩后

yes(); no();

12

yes();no();

6. 语句块压缩

函数体、with都会生成一个语句块,下边规则是针对语句块的压缩优化。

规则6.1 连续的表达式语句可以合并成一个逗号表达式

>>>留意:这里要表达式语句才可以。

压缩前

function A(){ B(); C(); d = 1; }

12345

function A(){   B();   C();   d = 1; }

压缩后

function A(){ B(), C(), d = 1; }

123

function A(){   B(), C(), d = 1; }

规则6.2 多个var声明可以压缩成一个var声明

压缩前

function A(){ var a; var b; var c; }

12345

function A(){   var a;   var b;  var c; }

压缩后

function A(){ var a, b, c; }

123

function A(){   var a, b, c; }

规则6.3 return之后的非变量声明以及非函数声明的语句可以去除

在块里边return之后的语句是不会被执行到的,所以可以被去除。但是Javascript中在块里边无论什么地方声明都可以(声明会被提升),因此return之后的声明是不能去掉的。 当然这里不仅仅是return之后的语句可以去除,还有throw、break、continue之后的语句也适用于这条规则。

压缩前

function A(){ return false; var a = 1; function B(){ } expr += 1; a = 3; }

123456

function A(){   return false;   var a = 1;   function B(){ }   expr += 1; a = 3; }

压缩后

function A(){ function B(){ } return false; var a = 1; }

12345

function A(){   function B(){ }   return false;   var a = 1; }

规则6.4 合并块末尾的return语句及其前边的多条表达式语句

其实这条规则看起来并不会使最后生成的代码缩小。

合并前

function A(){ B(); C(); return D(); }

12345

function A(){   B();   C();   return D(); }

合并后

function A(){ return B(), C(), D(); }

123

function A(){   return B(), C(), D(); }

7. IF分支优化

接下来开始复杂丰富多彩的IF分支压缩!

规则7.1 去除没用的if/else分支

如果if的条件是可预计算得到的常数结果,那么就可以忽略掉没用的if/else分支。

压缩前

if (true){ A(); }else{ B(); } if (false){ C(); }else{ D(); }

12345678910

if (true){   A();}else{   B(); } if (false){   C(); }else{   D(); }

压缩后

A(); D();

12

A(); D();

规则7.2 去除空的if/else分支

如果是if分支是空的话,把条件取非,else分支反转成if分支即可。

压缩前

if (A){ B(); }else{ } if (C){ }else{ D(); }

123456789

if (A){   B(); }else{ } if (C){ }else{   D(); }

压缩后

if (A){ B(); } if (!C){ D(); }

123456

if (A){   B(); } if (!C){   D(); }

规则7.3 尝试反转if/else分支,看看生成代码是否更短

尝试对if条件取非,如果能得到更短的代码,那就反转if/else分支。

压缩前

if (!c){ A(); }else{ B(); }

12345

if (!c){   A(); }else{   B(); }

压缩后

if (c){ B(); }else{ A(); }

12345

if (c){   B(); }else{   A(); }

规则7.4 如果if块里边只有一个if语句,并且else块为空,那么可以合并这两个if

压缩前

if (A){ if (B){ C(); } }else{ }

123456

if (A){   if (B){     C();   } }else{ }

压缩后

if (A && B){ C(); }

123

if (A && B){   C(); }

规则7.5 如果if最后一个语句是跳出控制语句,那么可以把else块的内容提到else外边,然后去掉else

压缩前

if (A){ B(); return; }else{ C(); }

123456

if (A){   B();   return; }else{   C();}

压缩后

if (A){ B(); return; } C();

12345

if (A){   B();   return; } C();

规则7.6 如果if/else里边都只有一句return语句,则可以合并这两句return

压缩前

if (A){ return B(); }else{ return C(); }

12345

if (A){   return B(); }else{   return C(); }

压缩后

return A ? B() : C();

1

return A ? B() : C();

规则7.7 如果if跟else里边都只有一句表达式语句,则可以化成条件表达式,然后走规则5.1跟5.2进一步压缩

即把适合的if语句转化为三目条件表达式,具体请参考规则5.1与5.2。

规则7.8 如果if/else其中一个块为空,另一个块只有一条语句,则可以化成||或者&&的表达式

压缩前

if (A){ B(); }else{ } if (C){ }else{ D(); }

123456789

if (A){   B(); }else{ } if (C){ }else{   D(); }

压缩后

A && B(); C || D();

12

A && B();C || D();

除特别注明外,本站所有文章均为慕白博客原创,转载请注明出处来自https://geekmubai.com/programming/185.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java一日一条

(转)Java中的System类

System类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang包。

10320
来自专栏C/C++基础

C++inline函数简介

inline函数是由inline关键字来定义,引入inline函数的主要原因是用它替代C中复杂易错不易维护的宏函数。

23420
来自专栏LIN_ZONE

JavaScript经典作用域问题(转载)

注:本文转自 javascript经典面试题 全局变量和局部变量 变量作用域 如需转载,请注明出处:https://www.cnblogs.com/zhuch...

9620
来自专栏python3

python While 循环语句

python 编程中 while 语句用于循环执行程序,即在某条件下,循环执行某段程序,以处理需要重复处理的相同任务。

16010
来自专栏程序员同行者

Python pass语句作用与用法

13720
来自专栏magicsoar

C++获取private的变量-偷走private

private提供了对数据的封装,使得private成员只能被类自身的成员函数以及类的友元访问,其他的函数或者类想要访问private成员只能通过该类所提供的s...

188100
来自专栏河湾欢儿的专栏

第一节预解释、作用域、this原理

10720
来自专栏专注数据中心高性能网络技术研发

[Effective Modern C++(11&14)]Chapter 2: auto

auto自动类型推导可以精简代码,避免隐式转换带来开销,同时增强程序可移植性和减少重构复杂性;但也由于与隐式代理类的冲突,造成了一些潜在问题,但是这些问题不是a...

35670
来自专栏大数据挖掘DT机器学习

Python一些基础面试题目总结

1 Python是如何进行内存管理的? 答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制 一、对象的引用计数机制 pytho...

39460
来自专栏前端知识分享

第193天:js---Math+Error+Number+Object总结

8320

扫码关注云+社区

领取腾讯云代金券