Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

由UglifyJS代码压缩想到的 #217

Open
confidence68 opened this issue Sep 18, 2016 · 0 comments
Open

由UglifyJS代码压缩想到的 #217

confidence68 opened this issue Sep 18, 2016 · 0 comments
Labels

Comments

@confidence68
Copy link
Owner

前言

最近休假了,所以博客文章和资源库一直没有更新。让大家久等了。今天我主要写写js代码压缩的一些小的细节吧!我之前的文章javascript实用技巧,js小知识,里面总结了一些js的小的技巧,今天这篇文章,主要是让我们理解js压缩的一些精华,以后我们在书写js过程中,可以写的更加简单,去除那些没有用的,多余的东西。当然,我今天不介绍js压缩中的单词简写。因为那样更加没有意义,让我们的代码变得晦涩难懂!

IF语句分支优化

我们看一下,在代码压缩过程中,if语句的分支是如何进行压缩的!

1 去除没用的if/else分支

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

压缩前

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

压缩后

A(); 
D();

2 去除空的if/else分支

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

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

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

压缩后

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

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

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

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

压缩后

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

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

压缩前

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

压缩后

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

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

压缩前

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

压缩后

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

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

压缩前

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

压缩后

return A ? B() : C();

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

压缩前

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

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

压缩后

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

8、可以使用hash方法缩减if语句

if (key == "博客") {
    val = "www.haorooms";
} else if (key == "资源库"){
    val = "resource.haorooms";
} else if (key == "工具"){
    val = "tool.haorooms";
}

可以如下写:

var rooms = {"博客":"www.haorooms", "资源库":"resource.haorooms", "工具":"tool.haorooms"};
val = rooms[key];

表达式的压缩

表达式我们平时用的也蛮多的,看看表达式是如何压缩的!

1 表达式预计算

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

压缩前

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

压缩后

var expr1 = 2;
var expr2 = 1 / 3;//由于计算出来的值0.3333333333333比1/3要长,所以不预计算

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;

压缩后

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

3 根据&&与||短路的特性压缩表达式

压缩前

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

压缩后

A();//返回&&第二个操作数的值
!1;//返回&&第一个操作数的值
!0;//返回||第一个操作数的值
A();//返回&&第二个操作数的值

运算符缩短

运算符缩短,就是将运算符简写!

1 对于二元操作符===以及!==,其两个操作数都是string类型或者都是布尔类型的,可以缩短成==以及!=

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

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

压缩后

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

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

这里说起来可能有点绕,但是想一下也很容易理解这条规则的详细判断: 必须是=号赋值语句;
=号左侧只能是变量,不能为表达式等;
=号右侧必须为二元操作表达式,并且符号是为数组[‘+’, ‘-‘, ‘/’, ‘*’, ‘%’, ‘>>’, ‘<<‘, ‘>>>’, ‘|’, ‘^’, ‘&’]中的元素;
=号右侧的二元表达式的第一个操作数必须跟=号左侧的变量一致。

压缩前

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

压缩后

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

3 操作符非!的压缩

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

压缩前

!(a>=b)
!!!a

压缩后

a<b
!a

去除没用的声明/引用

这里就是去除那些重复的声明或者引用!

1 去除重复的指示性字符串

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

压缩前

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

压缩后

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

2 去除没有使用的函数参数

参数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++;
}

3 去除函数表达式冗余的函数名

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

压缩前

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

压缩后

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

4 去除没用的块

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

压缩前

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

压缩后

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

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;
}

压缩后

switch(A){
  case 1: break;
  case 2://这里最后的break;可以忽略
}
label: switch(A){
  case 1: break;
  case 2: break label;//带标签的break不可以忽略
}

6 去除没有引用的label

压缩前

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

压缩后

var a = 1;
label2:while (true){
  break label2;//label2被引用 不可以去掉
}

7 去除没作用的toString函数调用

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

压缩前

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

压缩后

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

while压缩

对于while,可以省略不会执行的while循环、将循环巧妙变化节省字符

压缩前

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

压缩后

//while(false)被压缩忽略掉 
for(;;){ //while(true)转换成for(;;)节省4个字符 
  C(); 
  D(); 
}

小结

本文主要是参考https://github.com/WechatFE/UglifyJS-comment/blob/master/squeeze.js js压缩规则和细节,感兴趣的朋友可以去看看UglifyJS源码!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant