UglifyJS将if语句嵌入switch表达式的实现原理问询
我在用UglifyJS压缩代码时,发现它把部分if语句嵌套进switch表达式的写法很特殊,查了Mozilla和W3Schools的JS文档都没找到明确说明,虽然脚本运行正常,但想搞懂这个实现的原理。
相关代码
全局变量
var today = new Date();
原始脚本
function date(date = today) { if (date !== today) { date = new Date(date) }; var year = date.getFullYear().toString(), month = (date.getMonth() + 1).toString(), day = date.getDate().toString(), shortDay, longDay; if (month.length === 1) { month = "0" + month }; if (day.length === 1) { day = "0" + day }; switch (date.getDay()) { case 0: shortDay = 'Sun'; longDay = "Sunday"; break; case 1: shortDay = 'Mon'; longDay = "Monday"; break; case 2: shortDay = 'Tue'; longDay = "Tuesday"; break; case 3: shortDay = 'Wed'; longDay = "Wednesday"; break; case 4: shortDay = 'Thu'; longDay = "Thursday"; break; case 5: shortDay = 'Fri'; longDay = "Friday"; break; case 6: shortDay = 'Sat'; longDay = "Saturday"; break; }; return { full: year + '-' + month + '-' + day + ' ' + shortDay, mmddyyyy: month + '/' + day + '/' + year, mmddyy: month + '/' + day + '/' + year.substring(2), md: (date.getMonth() + 1).toString() + '/' + date.getDate().toString(), year, month, day, shortDay, longDay }; };
可读性格式化后的UglifyJS压缩脚本
function date(date = today) { var shortDay, longDay, year = (date = date !== today ? new Date(date) : date).getFullYear().toString(), month = (date.getMonth() + 1).toString(), day = date.getDate().toString(); switch (1 === month.length && (month = "0" + month), 1 === day.length && (day = "0" + day), date.getDay()) { case 0: shortDay = "Sun", longDay = "Sunday"; break; case 1: shortDay = "Mon", longDay = "Monday"; break; case 2: shortDay = "Tue", longDay = "Tuesday"; break; case 3: shortDay = "Wed", longDay = "Wednesday"; break; case 4: shortDay = "Thu", longDay = "Thursday"; break; case 5: shortDay = "Fri", longDay = "Friday"; break; case 6: shortDay = "Sat", longDay = "Saturday" } return { full: year + "-" + month + "-" + day + " " + shortDay, mmddyyyy: month + "/" + day + "/" + year, mmddyy: month + "/" + day + "/" + year.substring(2), md: (date.getMonth() + 1).toString() + "/" + date.getDate().toString(), year: year, month: month, day: day, shortDay: shortDay, longDay: longDay } }
核心原理解析
UglifyJS的这种写法其实是利用了JavaScript的两个原生语法特性:逗号运算符和逻辑与(&&)的短路特性,没有用到任何非标准语法,所以浏览器能正常执行。
1. 逗号运算符的作用
switch括号里的表达式是:
1 === month.length && (month = "0" + month), 1 === day.length && (day = "0" + day), date.getDay()
逗号运算符的规则是:依次执行每个逗号分隔的表达式,最后返回最后一个表达式的结果。所以这段代码会按顺序做三件事:
- 先执行
1 === month.length && (month = "0" + month):如果月份长度是1,就给月份补0; - 再执行
1 === day.length && (day = "0" + day):如果日期长度是1,就给日期补0; - 最后返回
date.getDay()的值,作为switch的判断依据。
这和原始脚本中先执行两个if补0,再用date.getDay()做switch判断的逻辑完全一致,只是把代码合并到了switch的表达式里,减少了代码行数。
2. 逻辑与(&&)的短路特性
上面的补0逻辑用了条件 && 赋值语句的写法,这是因为逻辑与的短路特性:如果前面的条件为false,就不会执行后面的语句;如果条件为true,就执行后面的语句并返回其结果。这和原始的if(条件){赋值}逻辑完全等价,只是写法更紧凑。
3. 其他压缩点的逻辑
原始脚本中if (date !== today) { date = new Date(date) }被替换成了:
date = date !== today ? new Date(date) : date
这是用三元运算符替换了if语句,并且把这个赋值表达式嵌入到了year的赋值链中,因为赋值表达式本身会返回赋值后的结果,所以可以直接链式调用.getFullYear().toString(),同样是利用表达式的执行特性来压缩代码。
总结
UglifyJS并没有发明新语法,只是把JavaScript原生的逗号运算符、逻辑短路、表达式赋值等特性组合起来,将分散的语句合并成更紧凑的写法,从而达到代码压缩的目的。这种写法虽然看起来特殊,但完全符合JS语法规范,所以能正常运行。
内容的提问来源于stack exchange,提问作者milklizard

