js断点和调试学习总结3

使用Chrome调试JavaScript的断点设置和调试技巧


你是怎么调试 JavaScript 程序的?最原始的方法是用 alert() 在页面上打印内容,稍微改进一点的方
法是用 console.log() 在 JavaScript 控制台上输出内容。嗯~,用这两种土办法确实解决了很多小型 
JavaScript 脚本的调试问题。不过放着 Chrome 中功能越发强大的开发者工具不用实在太可惜了。本文
主要介绍其中的 JavaScript断点设置和调试功能,也就是其中的 Sources Panel(以前叫 Scripts)。
如果你精通 Eclipse 中的各种 Java 调试技巧,那么这里的概念都是类似。写作本文时使用的 Chrome 
版本为 25.0.1364.172。


基本环境

SourcesPanel 的左边是内容源,包括页面中的各种资源。其中,又分 Sources 和 Content scripts。
Sources 就是页面本身包含的各种资源,它是按照页面中出现的域来组织的,这是我们要关注的。异步
加载的 js 文件,在加载后也会出现在这里的。Content scripts 是 Chrome 的一种扩展程序,它是按
照扩展的ID来组织的,这类扩展实际也是嵌入在页面中的资源,它们也可以读写DOM。编写、调试这类扩
展的开发者才要关心它们,如果你的浏览器没安装任何扩展,那么Content scripts 就看不到任何东西

Sources Panel 的中间主区域用于展示左边资源文件的内容。
Sources Panel 的右边是调试功能区,最上面的一排按钮分别是暂停/继续、单步执行、单步跳入、单步
跳出、禁用/启用所有断点。下面是各种具体的功能区。稍后介绍。
注意,左右两边的区域默认可能收缩在两侧没有显示出来,点击两侧的伸缩按钮展示出来。左边区域展
示出来时默认是自动收缩状态,点击旁边的 pin 按钮就不会缩回去了。
最下面还有一些功能按钮很有用。


在源代码上设置断点

通过左边的内容源,打开对应的 JavaScript 文件,鼠标点击文件的行号就可以设置和删除断点。添加
的每个断点都会出现在右侧调试区的 Breakpoints 列表中,点击列表中断点就会定位到内容区的断点上
。如果你有多个文件、多个断点的话,利用Breakpoints 列表中的断点快速定位非常方便。
对于每个已添加的断点都有两种状态:激活和禁用。刚添加的断点都是激活状态,禁用状态就是保留断
点但临时取消该断点功能。在Breakpoints 列表中每个断点前面都有一个复选框,取消选中就将禁用该
断点。断点位置的右键菜单中也可以禁用断点。也可以在右侧功能区上面按钮临时禁用所有已添加的断
点,再点一下恢复原状态。


条件断点:在断点位置的右键菜单中选择“Edit Breakpoint...”可以设置触发断点的条件,就是写一

个表达式,表达式为 true 时才触发断点。查看断点的环境调用栈(Call Stack):在断点停下来时,
右侧调试区的 Call Stack 会显示当前断点所处的方法调用栈,比如有一个函数 g() 其中又调用了函数 
f() ,而我在 f() 中设置了一个断点,那么我在 console 中执行函数 g() 的时候会触发断点,其调用
栈显示如下:
最上面是 f(),然后是 g()。调用栈中的每一层叫做一个 frame,点击每个 frame 可以跳到该 frame 
的调用点上。
此外,还可以在 frame 上用右键菜单重新开始执行当前 frame,也就是从该 frame 的开始处执行。比
如在函数 f() 的 frame 上 Restart Frame, 断点就会跳到 f() 的开头重新执行,context 中的变量
值也会还原。这样结合变量修改和编辑代码等功能,就可以在当前 frame 中反复进行调试,而不用刷新
页面重新触发断点了。查看变量

Call Stack 列表的下方是 Scope Variables 列表,在这里可以查看此时局部变量和全局变量的值。执

行选择的代码

在断点调试时,可以用鼠标选择想要查看的变量或表达式,然后右键菜单执行“Evaluate in Console”

,就可以看到该表达式的当前的值了。中断下次要执行的 JavaScript 语句右侧调试区的上面的“中断/

继续”按钮还有一个功能,在没有触发断点时,点一下这个按钮就会进入“准备”中断的状态,页面下

一次执行 JavaScript 语句时会自动中断,比如触发了一个点击动作时会执行的代码。临时修改 

JavaScript 代码通常我们在调试代码时习惯:修改代码→刷新页面→重新检查,这么一个循环。但其实 

Chrome 中可以临时修改 JS 文件中的内容,保存(Ctrl+S)就可以立即生效,结合 Console 等功能就

可以立即重新调试了。但注意这个修改是临时的,刷新页面修改就没了。
异常时断点
在界面下方能看到按钮,它是设置程序运行时遇到异常时是否中断的开关。点击该按钮会在3种状态间切
换:
默认遇到异常不中断
遇到所有异常都会中断,包括已捕获的情况
仅在出现未捕获的异常时才中断
主要解释一下状态2和状态3的区别
try{
throw 'a exception';
}catch(e){
console.log(e);
}

上面 try 里面的代码会遇到异常,但是后面的 catch 代码能够捕获该异常。如果是所有异常都中断,

那么代码执行到会产生异常的 throw 语句时就会自动中断;而如果是仅遇到未捕获异常才中断,那么这

里就不会中断。一般我们会更关心遇到未捕获异常的情况。
在 DOM 元素上设置断点
有时候我们需要监听某个 DOM 被修改情况,而不关心是哪行代码做的修改(也可能有多处都会对其做修

改)。那么我们可以直接在 DOM 上设置断点。


如图所见,在元素审查的 Elements Panel 中在某个元素上右键菜单里可以设置三种不同情况的断点:
子节点修改自身属性修改自身节点被删除选中之后,Sources Panel 中右侧的 DOM Breakpoints 列表中
就会出现该 DOM 断点。一旦执行到要对该 DOM 做相应修改时,代码就会在那里停下来,如下图所示。
XHR 断点
右侧调试区有一个 XHR Breakpoints,点击+ 并输入 URL 包含的字符串即可监听该 URL 的 Ajax 请求
,输入内容就相当于 URL 的过滤器。如果什么都不填,那么就监听所有 XHR 请求。一旦 XHR 调用触发
时就会在 request.send() 的地方中断。
按事件类型触发断点
右侧调试区的Event Listener 列表,这里列出了各种可能的事件类型。勾选对应的事件类型,当触发了

该类型的事件的 JavaScript 代码时就会自动中断。


调试快捷键
所有开发工具中的快捷键都可以在界面右下角的设置中查到。断点调试时一般用的是 F8、F10、F11或 
Shitf+F11,但在 Mac OS 上 F10 等功能键可能与系统默认的快捷键冲突。没关系,它们分别可以用 
Cmd+/ 、Cmd+'、Cmd+; 、Shift+Cmd+; 代替。//@ sourceURL 给 eval 出来的代码命名有时候一些非常
动态的代码是以字符串的形式通过 eval() 函数在当前 Javascript context 中创建出来,而不是作为
一个独立的 js 文件加载的。这样你在左边的内容区就找不到这个文件,因此很难调试。其实我们只要
在 eval 创建的代码末尾添加一行 “//@ sourceURL=name“就可以给这段代码命名(浏览器会特殊对待
这种特殊形式的注释),这样它就会出现在左侧的内容区了,就好像你加载了一个指定名字的 js 文件

一样,可以设置断点和调试了。下图中,我们通过 eval 执行了一段代码,并利用sourceURL 将它命名

为dynamicScript.js ,执行后左侧内容区就出现了这个“文件”,而它的内容就是 eval 的中的内容。

还可以看看这个给动态编译出来的CoffeeScript 代码命名的示例:
var coffee = CoffeeScript.compile(code.value)+ "//@ sourceURL=" + (evalName.value || 


"Coffeeeeeeee!");
eval(coffee);
实际上,//@ sourceURL 不仅仅可以用在 eval 的代码中,任何 js 文件、甚至是 Javascript Console 


输入的代码都可以用,效果一样!格式化代码(Pretty Print)按钮用于把杂乱的代码重新格式化为漂


亮的代码,比如一些已被压缩的 js 文件基本没法看、更没法调试。点一下格式化,再点一下就取消格


式化。
========

JavaScript调试技巧之console.log()详解



对于JavaScript程序的调试,相比于alert(),使用console.log()是一种更好的方式,原因在于:

alert()函数会阻断JavaScript程序的执行,从而造成副作用;而console.log()仅在控制台中打印相关

信息,因此不会造成类似的顾虑

一、什么是console.log()?
除了一些很老版本的浏览器,现今大多数浏览器都自带调试功能;即使没有调试功能,也可以通过安装

插件来进行补充。比如,老版本的Firefox没有自带调试工具,在这种情况下可以通过安装Firebug插件

来添加调试功能。在具备调试功能的浏览器上,window对象中会注册一个名为console的成员变量,指代

调试工具中的控制台。通过调用该console对象的log()函数,可以在控制台中打印信息。比如,以下代

码将在控制台中打印”Sample log”:
复制代码 代码如下:
window.console.log("Sample log");

上述代码可以忽略window对象而直接简写为:
复制代码 代码如下:
console.log("Sample log");

console.log()可以接受任何字符串、数字和JavaScript对象。与alert()函数类似,console.log()也可

以接受换行符\n以及制表符\t。console.log()语句所打印的调试信息可以在浏览器的调试控制台中看到

。不同的浏览器中console.log()行为可能会有所不同, 本文主要探讨Firebug中console.log()的使用 


二、兼容没有调试控制台的浏览器
对于缺少调试控制台的老版本浏览器,window中的console对象并不存在,因此直接使用console.log()

语句可能会在浏览器内部造成错误(空指针错误),并最终导致某些老版本浏览器的崩溃。为了解决这一

问题,可以人为定义console对象,并声明该console对象的log函数为空函数;这样,当console.log()

语句执行时,这些老版本的浏览器将不会做任何事情:
代码如下:
if(!window.console){
  window.console = {log : function(){}};
}


不过,在大多数情况下,没有必要去做这种兼容性工作 — console.log()等调试代码应当从最终的产品

代码中删除掉。
三、使用参数
与alert()函数类似,console.log()也可以接受变量并将其与别的字符串进行拼接:
复制代码 代码如下:
//Use variable
var name = "Bob";
console.log("The name is: " + name);

与alert()函数不同的是,console.log()还可以接受变量作为参数传递到字符串中,其具体语法与C语言

中的printf语法一致:
复制代码 代码如下:
//Use parameter
var people = "Alex";
var years = 42;
console.log("%s is %d years old.", people, years);

上述代码的执行结果为:”Alex is 42 years old.”
四、使用其它日志级别
除了console.log(),Firebug还支持多种不同的日志级别:debug、info、warn、error。以下代码将在

控制台中打印这些不同日志级别的信息:
代码如下:
//Use different logging level
console.log("Log level");
console.debug("Debug level");
console.info("Info level");
console.warn("Warn level");
console.error("Error level");


从Firebug控制台中可以看到,不同日志级别的打印信息,其颜色和图标是不一样的;同时,可以在控制

台中选择不同的日志级别来对这些信息进行过滤: 
========

JS调试必备的5个debug技巧


我一直使用printf调试程序,一般来说都是比较顺利,但有时候,你会发现需要更好的方法。下面几个


JavaScript技巧相信你一定会觉得十分有用

1. debugger;
我以前也说过,你可以在JavaScript代码中加入一句debugger;来手工造成一个断点效果。
需要带有条件的断点吗?你只需要用if语句包围它:
复制代码 代码如下:
if (somethingHappens) {
  debugger;
}


但要记住在程序发布前删掉它们。
2. 设置在DOM node发生变化时触发断点
有时候你会发现DOM不受你的控制,自己会发生一些奇怪的变化,让你很难找出问题的根源。
谷歌浏览器的开发工具里有一个超级好用的功能,专门可以对付这种情况,叫做“Break on…”,你在


DOM节点上右键,就能看到这个菜单项。
断点的触发条件可以设置成这个节点被删除、节点的属性有任何变化,或它的某个子节点有变化发生。


3. Ajax 断点
XHR断点,或Ajax断点,就像它们的名字一样,可以让我们设置一个断点,在特点的Ajax调用发生时触发

它们。
当你在调试Web应用的网络传输时,这一招非常的有效。



4. 移动设备模拟环境
谷歌浏览器里有一些非常有趣的模拟移动设备的工具,帮助我们调试程序在移动设备里的运行情况。
找到它的方法是:按F12,调出开发者工具,然后按ESC键(当前tab不能是Console),你就会看到第二层


调试窗口出现,里面的Emulation标签页里有各种模拟设备可选。
当然,这不会就变成了真正的iPhone,只是模拟了iPhone的尺寸,触摸事件和浏览器User Agent值。



5. 使用Audits改进你的网站
YSlow是一个非常棒的工具。谷歌浏览器的开发者工具里也有一个非常类似的工具,叫Audits。
它可快速的审计你的网站,给你提出非常实际有效的优化你的网站的建议和方法。


还有其它的吗?
没有这些工具,我不知道将如何开发。我还会写更多的关于这方面的技巧——一旦我有所发现,请关注

我的最新文章。
========

必备的JS调试技巧汇总



试想一下:出现了某个bug,有人用几分钟就搞定了,有人用了半天或者一天都找不到原因所在。你愿意


当前者还是后者呢?想当前者的就请好好看完本篇文章吧。文中涉及较多Gif演示动画请注意。


前言:任何一个编程者都少不了要去调试代码,不管你是高手还是菜鸟,调试程序都是一项必不可少的


工作。一般来说调试程序是在编写代码之后或测试期修改Bug 时进行的,往往在调试代码期间更加能够


体现出编程者的水平高低以及分析问题的准确度。不少初学者在寻找错误原因时,总是不得要领,花费


了大量时间却无法解决一些最终证明是相当简单的Bug。掌握各种调试技巧,必定能在工作中起到事半功


倍的效果。譬如,快速定位问题、降低故障概率、帮助分析逻辑错误等等。而在互联网前端开发越来越


重要的今天,如何在前端开发中降低开发成本,提升工作效率,掌握前端开发调试技巧尤为重要。
本文将一一讲解各种前端JS调试技巧,也许你已经熟练掌握,那让我们一起来温习,也许有你没见过的


方法,不妨一起来学习,也许你尚不知如何调试,赶紧趁此机会填补空白。
1、骨灰级调试大师Alert
那还是互联网刚刚起步的时代,网页前端还主要以内容展示为主,浏览器脚本还只能为页面提供非常简


单的辅助功能的时候。那个时候,网页主要运行在以IE6为主的浏览器中,JS的调试功能还非常弱,只能


通过内置于Window对象中的alert方法来调试,那时候看起来应该是这个样子:


需要说明一点,这里看到的效果,并非当年的IE浏览器中看到的效果,而是在高版本IE中的效果。此外


,当年貌似还没有这么高级的控制台,而alert的使用也是在真实的页面JS代码中。虽然,alert的调试


方式很原始,但当时确实有它不可磨灭的价值,甚至到今天,已然有其用武之地。
2、新一代调试王者Console
随着JS在Web前端中能做的事情越来越多,责任越来越大,而地位也越来越重要。传统的alert调试方式


已经渐渐不能满足前端开发的种种场景。而且alert调试方式弹出的调试信息,那个窗口着实不太美观,


而且会遮挡部分页面内容,着实有些不太友好。
另一方面,alert的调试信息,必须在程序逻辑中添加类似"alert(xxxxx)"这样的语句,才能正常工作,


并且alert会阻碍页面的继续渲染。这就意味着开发人员调试完成后,必须手动清除这些调试代码,实在


有些麻烦。
所以,新一代的浏览器Firefox、Chrome,包括IE,都相继推出了JS调试控制台,支持使用类


似"console.log(xxxx)"的形式,在控制台打印调试信息,而不直接影响页面显示。以IE为例,它看起来


像这样:


好吧,再见丑陋的alert弹出框。而以Chrome浏览器为首的后起之秀,为Console扩展了更丰富的功能:


你以为这样就满足了?Chrome开发团队的想象力实在不得不让人佩服:

好了,稍微多说了一点点题外话。总之,控制台以及浏览器内置Console对象的出现,给前端开发调试带


来了极大的便利。
有人会问,这样的调试代码不一样需要在调试完成后进行清理吗?
关于这个问题,如果在使用console对象之前先进性存在性验证,其实不删除也不会对业务逻辑造成破坏


。当然,为了代码整洁,在调试完成后,还是应尽可能删除这些与业务逻辑无关的调试代码。
3、JS断点调试
断点,调试器的功能之一,可以让程序中断在需要的地方,从而方便其分析。也可以在一次调试中设置


断点,下一次只需让程序自动运行到设置断点位置,便可在上次设置断点的位置中断下来,极大的方便

了操作,同时节省了时间。——百度百科
JS断点调试,即是在浏览器开发者工具中为JS代码添加断点,让JS执行到某一特定位置停住,方便开发


者对该处代码段的分析与逻辑处理。为了能够观察到断点调试的效果,我们预先随意准备一段JS代码:


代码很简单,就是定义一个函数,传入两个数,分别加上一个乱七八糟的随机整数后,再返回两个数的


总和。以Chrome开发者工具为例,我们来看一下JS断点调试的基本方法。
3.1、Sources断点
首先,测试代码中我们通过上图console的输出结果可以看出代码应该是正常运行了,但是为什么是应该


呢?因为函数中加了一个随机数,而最终结果是否真的是正确的呢?这是毫无意义的猜想,但是假设我


现在就是要验证一下:函数传入的两个数、被加的随机数,以及最终的总和。那么该怎么操作呢?
方法一,前面讲过最普通的,无论使用alert还是console,我们可以这么来验证:


从上图发现,我们在代码中新增了三行console代码,用以打印我们关心的数据变量,而最终我们从控制


台(Console面板)中的输出结果,可以很清楚的验证整个计算过程是否正常,进而达到我们题设的验证


要求。
方法二,方法一的验证过程存在很明显的弊端就是,添加了很多冗余代码,接下来我们看一下使用断点


进行验证,是否更加方便,先看一个如何加断点,以及断点后是什么效果:


如图,给一段代码添加断点的流程是"F12(Ctrl + Shift + I)打开开发工具"——"点击Sources菜单"


——"左侧树中找到相应文件"——"点击行号列"即完成在当前行添加/删除断点操作。当断点添加完毕后


,刷新页面JS执行到断点位置停住,在Sources界面会看到当前作用域中所有变量和值,只需对每个值进


行验证即可完成我们题设验证要求。
那问题来了,仔细的朋友会发现当我的代码执行到断点的时候,显示的变量a和b的值是已经进行过加法


运算后的,我们看不到调用sum函数时初始传入的10和20。那么该怎么办呢?这就要回过头来先学习一下


断点调试的一些基础知识了。我们打开Sources面板后其实会在界面中看到如下内容,我们跟着鼠标轨迹


逐一看看都是什么意思:


从左到右,各个图标表示的功能分别为:
Pause/Resume script execution:暂停/恢复脚本执行(程序执行到下一断点停止)。
Step over next function call:执行到下一步的函数调用(跳到下一行)。
Step into next function call:进入当前函数。
Step out of current function:跳出当前执行函数。
Deactive/Active all breakpoints:关闭/开启所有断点(不会取消)。
Pause on exceptions:异常情况自动断点设置。
到此,断点调试的功能键介绍得差不多了,接下来我们就可以一行一行去看我们的程序代码,查看每一


行执行完毕之后,我们各个变量的变化情况了,如下图所示:


如上,我们可以看到a、b变量从最初值,到中间加上随机值,再到最后计算总和并输出最终结果的整个


过程,完成题设验证要求不在话下。
其余几个功能键,我们稍微改动一下我们的测试代码,用一张gif图来演示他们的使用方法:


这里需要注意一点,直接在代码区打印变量值的功能是在较新版本的Chrome浏览器中才新增的功能,如


果你还在使用较老版本的Chrome浏览器,可能无法直接在断点的情况下查看变量信息,此时你可以将鼠


标移动到变量名上短暂停顿则会出现变量值。也可以用鼠标选中变量名称,然后右键"Add to watch"在


Watch面板查看,此方法同样适用于表达式。此外,你还可以在断点情况下,切换到Console面板,直接


在控制台输入变量名称,回车查看变量信息。该部分比较简单,考虑篇幅问题,不在做图演示。
3.2、Debugger断点
所谓的Debugger断点,其实是我自己给它命名的,专业术语我也不知道怎么说。具体的说就是通过在代


码中添加"debugger;"语句,当代码执行到该语句的时候就会自动断点。接下去的操作就跟在Sources面


板添加断点调试几乎一模一样,唯一的区别在于调试完后需要删除该语句。
既然除了设置断点的方式不一样,功能和Sources面板添加断点效果一样,那么为什么还会存在这种方式


呢?我想原因应该是这样的:我们在开发中偶尔会遇到异步加载html片段(包含内嵌JS代码)的情况,


而这部分JS代码在Sources树种无法找到,因此无法直接在开发工具中直接添加断点,那么如果想给异步


加载的脚本添加断点,此时"debugger;"就发挥作用了。我们直接通过gif图看看他的效果:


4、DOM断点调试
DOM断点,顾名思义就是在DOM元素上添加断点,进而达到调试的目的。而在实际使用中断点的效果最终


还是落地到JS逻辑之内。我们依次来看一下每一种DOM断点的具体效果。
4.1、当节点内部子节点变化时断点(Break on subtree modifications)
在前端开发越来越复杂的今天,前端JS代码越来越多,逻辑越来越复杂,一个看似简单的Web页面,通常


伴随着大段大段的JS代码,涉及诸多DOM节点增、删、改的操作。难免遇到直接通过JS代码很难定位代码


段的情况,而我们却可以通过开发者工具的Elements面板,快速定位到相关DOM节点,这时候通过DOM断


点定位脚本就显得尤其重要了。具体我们还是通过gif演示来看一下吧:


上图演示了对ul子节点(li)的增加、删除以及交换顺序操作触发断点的效果。但需要注意的是,对子


节点进行属性修改和内容修改并不会触发断点。
4.2、当节点属性发生变化时断点(Break on attributes modifications)
另一方面,由于前端处理的业务逻辑越来越复杂,对一些数据的存储依赖越来越强烈,而将临时数据存


储于DOM节点的(自定义)属性中,是很多情况下开发者优先选择的方式。特别是在HTML5标准增强自定


义属性支持(例:dataset、data-*之类)之后,属性设置应用越来越多,因此Chrome开发者工具也提供


了属性变化断点支持,其效果大致如下:


此方式同样需要注意,对子节点的属性进行任何操作也不会触发节点本身的断点。
4.3、当节点被移除时断点(Break on node removal)
这个DOM断点设置很简单,触发方式很明确——当节点被删除时。所以通常情况应该是在执


行"parentNode.removeChild(childNode)"语句的时候使用此方式。此方式使用不多。
前面介绍到的基本上是我们在日常开发中经常用到的调试手段,运用得当它们也几乎能应对我们日常开


发中的几乎所有问题。但是,开发者工具还考虑到了更多的情况,提供更多的断点方式,如图:


5、XHR Breakpoints
这几年前端开发发生了翻天覆地的变化,从当初的名不见经传到如今的盛极一时,Ajax驱动Web富应用,


移动WebApp单页应用风生水起。这一切都离不开XMLHttpRequest对象,而"XHR Breakpoints"正是专为异


步而生的断点调试功能。


我们可以通过"XHR Breakpoints"右侧的"+"号为异步断点添加断点条件,当异步请求触发时的URL满足此


条件,JS逻辑则会自动产生断点。演示动画中并没有演示到断点位置,这是因为,演示使用的是jQuery


封装好的ajax方法,代码已经过压缩,看不到什么效果,而事实上XHR断点的产生位置是"xhr.send()"语


句。
XHR断点的强大之处是可以自定义断点规则,这就意味着我们可以针对某一批、某一个,乃至所有异步请


求进行断点设置,非常强大。但是,似乎这个功能在日常开发中用得并不多,至少我用得不多。想想原


因大概有两点:其一,这类型的断点调试需求在日常业务中本身涉及不多;其二,现阶段的前端开发大


多基于JS框架进行,最基本的jQuery也已经对Ajax进行了良好封装,极少有人自己封装Ajax方法,而项


目为了减少代码体积,通常选择压缩后的代码库,使得XHR断点跟踪相对不那么容易了。
6、Event Listener Breakpoints
事件监听器断点,即根据事件名称进行断点设置。当事件被触发时,断点到事件绑定的位置。事件监听


器断点,列出了所有页面及脚本事件,包括:鼠标、键盘、动画、定时器、XHR等等。极大的降低了事件


方面业务逻辑的调试难度。


演示实例演示了当click事件被触发时和当setTimeout被设置时的断点效果。实例显示,当选中click事


件断点之后,两个按钮的被点击时都触发了断点,而当setTimeout被设置时,"Set Timer"断点被触发。
调试,是在项目开发中非常重要的环节,不仅可以帮助我们快速定位问题,还能节省我们的开发时间。


熟练掌握各种调试手段,定当为你的职业发展带来诸多利益,但是,在如此多的调试手段中,如何选择


一个适合自己当前应用场景的,这需要经验,需要不断尝试积累。
写到这里,基本上可以说是倾囊而出了,希望能引起你的注意,希望能够让你感到一丝的触动,感到一


些似曾相识。最主要的我还是希望你能够快速提高自己的技能,让自己成为技术牛人!
========

JS高级调试技巧:捕获和分析 JavaScript Error详解



前端工程师都知道 JavaScript 有基本的异常处理能力。我们可以 throw new Error(),浏览器也会在


我们调用 API 出错时抛出异常。但估计绝大多数前端工程师都没考虑过收集这些异常信息


反正只要 JavaScript 出错后刷新不复现,那用户就可以通过刷新解决问题,浏览器不会崩溃,当没有


发生过好了。这种假设在 Single Page App 流行之前还是成立的。现在的 Single Page App 运行一段


时间后状态复杂无比,用户可能进行了若干输入操作才来到这里的,说刷新就刷新啊?之前的操作岂不


要完全重做?所以我们还是有必要捕获和分析这些异常信息的,然后我们就可以修改代码避免影响用户


体验。
捕获异常的方式
我们自己写的 throw new Error() 想要捕获当然可以捕获,因为我们很清楚 throw 写在哪里了。但是


调用浏览器 API 时发生的异常就不一定那么容易捕获了,有些 API 在标准里就写着会抛出异常,有些 


API 只有个别浏览器因为实现差异或者有缺陷而抛出异常。对于前者我们还能通过 try-catch 捕获,对


于后者我们必须监听全局的异常然后捕获。
try-catch
如果有些浏览器 API 是已知会抛出异常的,那我们就需要把调用放到 try-catch 里面,避免因为出错


而导致整个程序进入非法状态。例如说 window.localStorage 就是这样的一个 API,在写入数据超过容


量限制后就会抛出异常,在 Safari 的隐私浏览模式下也会如此。
try {
 localStorage.setItem('date', Date.now());
} catch (error) {
 reportError(error);
}
另一个常见的 try-catch 适用场景是回调。因为回调函数的代码是我们不可控的,代码质量如何,会不


会调用其它会抛出异常的 API,我们一概不知道。为了不要因为回调出错而导致调用回调后的其它代码


无法执行,所以把调用回到放到 try-catch 里面是必须的。
listeners.forEach(function(listener) {
 try {
 listener();
 } catch (error) {
 reportError(error);
 }
});
window.onerror
对于 try-catch 覆盖不到的地方,如果出现异常就只能通过 window.onerror 来捕获了。
window.onerror =
 function(errorMessage, scriptURI, lineNumber) {
 reportError({
 message: errorMessage,
 script: scriptURI,
 line: lineNumber
 });
}
注意不要耍小聪明使用 window.addEventListener 或 window.attachEvent 的形式去监听 


window.onerror。很多浏览器只实现了 window.onerror,或者是只有 window.onerror 的实现是标准的


。考虑到标准草案定义的也是 window.onerror,我们使用 window.onerror 就好了。
属性丢失
假设我们有一个 reportError 函数用来收集捕获到的异常,然后批量发送到服务器端存储以便查询分析


,那么我们会想要收集哪些信息呢?比较有用的信息包括:错误类型(name)、错误消息(message)、


脚本文件地址(script)、行号(line)、列号(column)、堆栈跟踪(stack)。如果一个异常是通过 


try-catch 捕获到的,这些信息都在 Error 对象上(主流浏览器都支持),所以 reportError 也能收


集到这些信息。但如果是通过 window.onerror 捕获到的,我们都知道这个事件函数只有 3 个参数,所


以这 3 个参数意外的信息就丢失了。
序列化消息
如果 Error 对象是我们自己创建的话,那么 error.message 就是由我们控制的。基本上我们把什么放


进 error.message 里面,window.onerror 的第一个参数(message)就会是什么。(浏览器其实会略作


修改,例如加上 'Uncaught Error: ' 前缀。)因此我们可以把我们关注的属性序列化(例如 


JSON.Stringify)后存放到 error.message 里面,然后在 window.onerror 读取出来反序列化就可以了


。当然,这仅限于我们自己创建的 Error 对象。
第五个参数
浏览器厂商也知道大家在使用 window.onerror 时受到的限制,所以开始往 window.onerror 上面添加


新的参数。考虑到只有行号没有列号好像不是很对称的样子,IE 首先把列号加上了,放在第四个参数。


然而大家更关心的是能否拿到完整的堆栈,于是 Firefox 说不如把堆栈放在第五个参数吧。但 Chrome 


说那还不如把整个 Error 对象放在第五个参数,大家想读取什么属性都可以了,包括自定义属性。结果


由于 Chrome 动作比较快,在 Chrome 30 实现了新的 window.onerror 签名,导致标准草案也就跟着这


样写了。
window.onerror = function(
 errorMessage,
 scriptURI,
 lineNumber,
 columnNumber,
 error
) {
 if (error) {
 reportError(error);
 } else {
 reportError({
 message: errorMessage,
 script: scriptURI,
 line: lineNumber,
 column: columnNumber
 });
 }
}
属性正规化
我们之前讨论到的 Error 对象属性,其名称都是基于 Chrome 命名方式的,然而不同浏览器对 Error 


对象属性的命名方式各不相同,例如脚本文件地址在 Chrome 叫做 script 但在 Firefox 叫做 


filename。因此,我们还需要一个专门的函数来对 Error 对象进行正规化处理,也就是把不同的属性名


称都映射到统一的属性名称上。具体做法可以参考这篇文章。尽管浏览器实现会更新,但人手维护一份


这样的映射表并不会太难。
类似的是堆栈跟踪(stack)的格式。这个属性以纯文本的形式保存一份异常在发生时的堆栈信息,由于


各个浏览器使用的文本格式不一样,所以也需要人手维护一份正则表达,用于从纯文本中提取每一帧的


函数名(identifier)、文件(script)、行号(line)和列号(column)。
安全限制
如果你也遇到过消息为 'Script error.' 的错误,你会明白我在说什么的,这其实是浏览器针对不同源


(origin)脚本文件的限制。这个安全限制的理由是这样的:假设一家网银在用户登录后返回的 HTML 


跟匿名用户看到的 HTML 不一样,一个第三方网站就能把这家网银的 URI 放到 script.src 属性里面。


HTML 当然不可能被当做 JS 解析啦,所以浏览器会抛出异常,而这个第三方网站就能通过解析异常的位


置来判断用户是否有登录。为此浏览器对于不同源脚本文件抛出的异常一律进行过滤,过滤得只剩下 


'Script error.' 这样一条不变的消息,其它属性统统消失。
对于有一定规模的网站来说,脚本文件放在 CDN 上,不同源是很正常的。现在就算是自己做个小网站,


常见框架如 jQuery 和 Backbone 都能直接引用公共 CDN 上的版本,加速用户下载。所以这个安全限制


确实造成了一些麻烦,导致我们从 Chrome 和 Firefox 收集到的异常信息都是无用的 'Script error.'



CORS
想要绕过这个限制,只要保证脚本文件和页面本身同源即可。但把脚本文件放在不经 CDN 加速的服务器


上,岂不降低用户下载速度?一个解决方案是,脚本文件继续放在 CDN 上,利用 XMLHttpRequest 通过 


CORS 把内容下载回来,再创建 <script> 标签注入到页面当中。在页面当中内嵌的代码当然是同源的啦



这说起来很简单,但实现起来却有很多细节问题。用一个简单的例子来说:
<script src="http://cdn.com/step1.js"></script>
<script>
 (function step2() {})();
</script>
<script src="http://cdn.com/step3.js"></script>
我们都知道这个 step1、step2、step3 如果存在依赖关系的话,则必须严格按照这个顺序执行,否则就


可能出错。浏览器可以并行请求 step1 和 step3 的文件,但在执行时顺序是保证的。如果我们自己通


过 XMLHttpRequest 获取 step1 和 step3 的文件内容,我们就需要自行保证其顺序正确性。此外不要


忘记了 step2,在 step1 以非阻塞形式下载的时候 step2 就可以被执行了,所以我们还必须人为干预 


step2 让它等待 step1 完成后再执行。
如果我们已经有一整套工具来生成网站上不同页面的 <script> 标签的话,我们就需要调整一下这套工


具让它对 <script> 标签做出改动:
<script>
 scheduleRemoteScript('http://cdn.com/step1.js');
</script>
<script>
 scheduleInlineScript(function code() {
 (function step2() {})();
 });
</script>
<script>
 scheduleRemoteScript('http://cdn.com/step3.js');
</script>
我们需要实现 scheduleRemoteScript 和 scheduleInlineScript 这两个函数,并且保证它们在第一个


引用外部脚本文件的 <script> 标签之前就被定义好,然后余下的 <script> 标签都会被改写成上面这


种形式。注意原本立即执行的 step2 函数被放到了一个更大的 code 函数里面了。code 函数并不会被


执行,它只是一个容器而已,这样使得原本 step2 的代码不需要转义就能保留下来,但又不会被立即执


行。
接下来我们还需要实现一套完整的机制,保证这些由 scheduleRemoteScript 根据地址下载回来的文件


内容和由 scheduleInlineScript 直接获取到的代码能够按照正确的顺序一个接一个地执行。详细的代


码我就不在这里给出了,大家有兴趣可以自己去实现。
行号反查
通过 CORS 获取内容再把代码注入页面能够突破安全限制,但会引入一个新的问题,那就是行号冲突。


原本通过 error.script 可以定位到唯一的脚本文件,再通过 error.line 可以定位到唯一的行号。现


在由于都是页面内嵌的代码,多个 <script> 标签并不能通过 error.script 来区分,然而每一个 


<script> 标签内部的行号都是从 1 算起的,结果就导致我们无法利用异常信息定位错误所在的源代码


位置。
为了避免行号冲突,我们可以浪费一些行号,使得每一个 <script> 标签中有实际代码所使用的行号区


间互相不重叠。举个例子来说,假设每个 <script> 标签中的实际代码都不超过 1000 行,那么我可以


让第一个 <script> 标签中的代码占用第 1–1000 行,让第二个 <script> 标签中的代码占用第 1001


–2000 行(前面插入 1000 行空行),第三个 <script> 标签种的代码占用第 2001–3000 行(前面插


入 2000 行空行),以此类推。然后我们使用 data-* 属性记录这些信息,便于反查。
<script
 data-src="http://cdn.com/step1.js"
 data-line-start="1"
>
 // code for step 1
</script>
<script data-line-start="1001">
 // '\n' * 1000
 // code for step 2
</script>
<script
 data-src="http://cdn.com/step3.js"
 data-line-start="2001"
>
 // '\n' * 2000
 // code for step 3
</script>
经过这样处理后,如果一个错误的 error.line 是 3005 的话,那意味着实际的 error.script 应该是 


'http://cdn.com/step3.js',而实际的 error.line 则应该是 5。我们可以在之前提到的 reportError 


函数里面完成这项行号反查工作。
当然,由于我们没办法保证每一个脚本文件只有 1000 行,也有可能有些脚本文件明显小于 1000 行,


所以其实不需要固定分配 1000 行的区间给每一个 <script> 标签。我们可以根据实际脚本行数来分配


区间,只要保证每一个 <script> 标签所使用的区间互不重叠就可以了。
crossorigin 属性
浏览器对于不同源的内容进行的安全限制当然不仅限于 <script> 标签。既然 XMLHttpRequest 可以通


过 CORS 来突破这个限制,为什么直接通过标签引用的资源就不可以呢?这当然是可以的。
针对 <script> 标签引用不同源脚本文件的限制同样作用于 <img> 标签引用不同源图片文件。如果一个 


<img> 标签是不同源的话,一旦在 <canvas> 绘图时用到了,该 <canvas> 将变为只写状态,保证网站


不能通过 JavaScript 窃取未授权的不同源图片数据。后来 <img> 标签通过引入 crossorigin 属性解


决了这个问题。如果使用 crossorigin="anonymous",则相当于匿名 CORS;如果使用 `crossorigin=“


use-credentials”,则相当于带认证的 CORS。
既然 <img> 标签能这样做,为什么 <script> 标签就不能这样做?于是浏览器厂商就为 <script> 标签


加入了同样的 crossorigin 属性用于解决上述安全限制问题。现在 Chrome 和 Firefox 对这个属性的


支持是完全没有问题的。Safari 则会把 crossorigin="anonymous" 当做 crossorigin="use-


credentials" 处理,结果是如果服务器只支持匿名 CORS 则 Safari 会当做认证失败。由于 CDN 服务


器出于性能的考虑被设计为只能返回静态内容,不可能动态的根据请求返回认证 CORS 所需的 HTTP 


Header,Safari 相当于不能利用此特性来解决上述问题。
总结
JavaScript 异常处理看起来很简单,跟其它语言没什么区别,但真的要把异常都捕获了然后对属性做分


析,其实还不是那么容易的事情。现在尽管有一些第三方服务提供捕获 JavaScript 异常的类 Google 


Analytics 服务,但如果要弄明白其中的细节和原理还是必须自己亲手做一次。
========

灵活应用js调试技巧解决样式问题的步骤分享



在很多时候,前端开发人员使用JS脚本,对页面Dom结构或者是样式做出了修改,会造成一些意想不到的


bug


由于种种原因,例如:代码逻辑复杂、时间久了遗忘处理细节、或者根本就是接手修改别人遗留的bug,


在这种情况下,debug就会变成一件头疼的事情。 


在此分享一些JS调试方面的技巧,针对各种疑难杂症,往往能起到较好的效果。 


Step 1:检查服务器直接render出来的内容 


    使用查看源文件的方式,这一步首先明确,页面HTML片段是否在服务器端就已经不正常了。 


Step 2:比较实际的HTML内容和服务器render出来的原始内容之间的差异 


    可以使用一些前端工具(例如:IE下的开发人员工具、Firebug、Chrome的开发人员工具等),


实时查看当前HTML片段内容 


Step 3:在合适的位置增加debugger 


    例如:先找出大概可能出问题的js代码,在合适的地方加debugger,或者是使用工具增加类似


于“在属性被修改时中断”的断点,例如实际的HTML比原始内容多出了一个width属性 


Step 4:运行你的页面,进入中断,并检查js调用堆栈(关键的一步) 


    推荐使用IE支持的Visual Studio调试器(需要先设置IE:取消“禁用脚本调试”),这时基本


就能确定是哪个js方法修改了样式
========
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值