12月阅读周·编写可测试的JavaScript代码:调试之生产环境调试篇
背景
去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。
没有计划的阅读,收效甚微。
新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。
这个“玩法”虽然常见且板正,但是有效,已经坚持阅读十一个月。
已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》、《前端架构:从入门到微前端》、《秒懂算法:用常识解读数据结构与算法》、《JavaScript权威指南》、《JavaScript异步编程设计快速响应的网络应用》。
当前阅读周书籍:《编写可测试的JavaScript代码》。
生产环境调试
最小化、组合和混淆让生产代码很难调试。我们已经讨论过,WebKit浏览器有一个“prettify”按钮可以将缩小代码展开(unwrap),但是对于组合和混淆代码呢?或者说,利用另外一种工具使用其他语言,比如CoffeScrit甚至是Java(通过GWT)生成的JavaScript代码又如何呢?
最小化代码
WebKit浏览器有一个“de-minify”按钮,是用于压缩代码的最简单的例子。图7-27展示了Chrome调试器里的最小化代码。
没办法设置断点,如果代码停在某处,是不可能告诉我们确切的位置的。
点击{}按钮,解压代码。此时的代码是具可读性的,并且可以在每一行进行断点设置。但由于这段代码也被Google的Closure Compiler混淆过,所以我们没办法访问原始变量名(类似的混淆工具会产生类似的结果)。
源映像
源映像(source map)可以拯救我们。一个源映像是一个包含大JSON对象的文件,该对象将压缩过的、混淆过的或组合过的JavaScript代码行与原始代码进行映射。一个额外的好处是该“原始代码”不一定是JavaScript!可以查看源映像规范。
使用源映像,被压缩过的、混淆过的或组合过的JavaScript代码可以在调试器里映射到原始代码。本文撰写时,只有Google Chrome原生支持源映像(Beta分支),尽管其他浏览器也会紧随其后。Google Closure Compiler可以组合并生成压缩和混淆的代码,并且还可以对代码输出源映像文件。Google还提供了Closure Inspector,它是一个可以让Firebug理解源映像文件的Firebug扩展。希望不久的将来所有的浏览器都原生支持源映像。
看一下使用过程。在本例中,我们将使用前面看到的getIterator函数。这里它还是在文件iterator.js中:
function getIterator(countBy, startAt, upTill) {
countBy = countBy || 1;
startAt = startAt || 0;
upTill = upTill || 100;
var current = startAt;
var ret = function () {
current += countBy;
return current > upTill ? NaN : current;
};
ret.displayName = 'Iterator from ' + startAt + ' until ' + upTill + ' by ' + countBy;
return ret;
}
让我们从Google Closure Compiler主页下载最新版本的compiler.jar。现在,可以在iterator.js上运行Closure Compiler了:
java-jar compiler.jar --js iterator.js --js_output_file it-comp.js
该文件除了删除了所有空格(和评论)以外,变量名称也被重命名了:countBy变成了a,startAt变成了b,upTill变成了c,等等。这极大地减少了文件的大小,现在就可以部署到生产环境中了!
就这样吗?这段代码仍然有一个问题:在浏览器中调试该代码会非常困难。即便在WebKit调试器里使用{}按钮对它进行“unminifying”,也不会将其变成原始代码,因为变量已经被重命名了。
幸运的是,Closure Compiler可以使用更高级的转换方式将代码变得更小。然而,这些转换将进一步偏离我们原来的代码。
在这里,它就成了一个问题。如果将该代码作为Web页面的一部分加载到调试器并尝试调试时,我们可以看到代码很难看。
源映像来发力了!首先告诉Closure Compiler为我们生成一个源映像。
如果我们查看生成的源映像文件,就会发现,it-min.js.map确实是一个巨大的JSON对象,该对象的核心是mappings属性,它是一个Base64 VLQ编码的字符串。
最后一点神奇的地方就是将源映像链接到JavaScript代码给浏览器提供服务,通过添加一个额外的HTTP头或在JavaScript压缩中添加一行技巧代码,告诉调试器在哪里可以找到相应的源映像。这里,我们在Closure Compiler输出添加一行代码,将其指向到编译器生成的源映像文件。现在回到浏览器,加载文件并查看调试器。
调试器窗口中显示的不仅是我们确切的原始代码,而且下拉菜单中的文件名也是原来的文件名。断点设置就像在浏览器中的真实代码上设置的一样,但事实上浏览器运行的代码是Closure Compiler输出的最小化代码。
使用源映像的最大问题是像原始代码一样进行的打印/执行表达式,但因为执行的是最小化代码,所以这些原始变量名在浏览器中实际上根本不存在。我们不能对表达式求值从而确定变量的值,我们也不知道变量名被映射成哪个名字了。
在调试器中,我看到了变量current是在哪里定义的,但是我的监控表达式(Watch Expressions)显示它并没有定义!相反,却有一些随机的变量名,如初始值为0的变量d,但它似乎没有在源代码清单里定义。这很奇怪,并且需要注意。或许未来版本的规范可能会处理这种不一致性。
Google的GWT编译器也可以输出源映像,并且也适用于CoffeeScript编译器,那么在浏览器运行编译版本的代码时,就可以说我们在调试原始原生代码。
总结
使用源映像文件可以直接对生产环境和非JavaScript进行调试。
作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏⭐️ | 留言📝。
- 点赞
- 收藏
- 关注作者
评论(0)