错误处理与调试
浏览器错误报告
所有主流桌面浏览器都提供了向用户报告错误的机制;默认情况下,所有浏览器都会隐藏错误信息;因为除了开发者外这些信息对其他人没有什么用,还有就是网页在正常操作中报错的固有特性
桌面控制台
所有的现代桌面浏览器都会通过控制台暴露错误,不同浏览器进入控制台方式不一样,详情看红宝书p675
移动控制台
浏览器不会直接在移动设备上提供控制台界面
错误处理
有良好的错误处理策略可以让用户知道发生了什么;为此必须理解各种捕获和处理JavaScript错误方式
try/catch语句
ECMA-262新增了try/catch语句,作为在JavaScript中处理异常的一种方式
如果try块中有代码发生错误,代码会立即退出执行,并跳到catch中;catch会接收到一个对象,该对象包含发生的错误相关信息;与其他语言不同,即使在catch块中不使用错误对象,也必须为它定义名称;错误对象中暴露的实际信息因浏览器而异,但是少包含错误消息的message属性;ECMA-262也指定了错误类型的name属性
1、finally子句
finally中的代码始终运行,try或catch中的代码无法阻止,包括return语句
2、错误类型
Error:基类型,其他的错误都继承该类型;浏览器很少会抛出该类型错误,该类型主要用于开发者抛出自定义错误
InternalError:底层js引擎发生抛出异常时由浏览器抛出
EvalError:eval()的使用与定义不一致
RangeError:数值越界
ReferenceError:非法或不能识别的引用值
SyntaxError:发生语法解析错误
TypeError:操作数据类型错误
URIError:URI处理函数使用不当(encodeURI()和decodeURI())
3、try/catch用法
一般来说,在运用了无法修改的可能有问题的代码时,就可以用该语句,但是当明确了某个错误很可能会发生时,建议修改代码防止错误而不是使用该语句捕获错误
抛出错误
与try/catch对应的机制是throw操作符,用于在任何时候抛出自定义错误,throw操作符必须有一个值,但是值的类型不限
throw 123;
throw "hello";
throw true;
throw { name: "lsn" };
使用throw时,代码立即停止执行,除非使用try/catch捕获了抛出的值
可以通过内置的错误类型来模拟浏览器错误,每种错误类型的构造函数都只接收一个参数,就是错误消息
throw new Error('xxx');
throw new TypeError('xxx');
通过继承Error可以自定义错误类型,需要提供那么属性和message属性
1、何时抛出错误
在某个代码中自定义错误并给出错误位置提示可以有效地找到错误相关代码
2、抛出错误与try/catch
捕获错误的目的是阻止浏览器以其默认方式响应;抛出错误的目的是为错误提供有关其发生的原因说明
error事件
任何没有被try/catch语句处理的错误都会在window对象上触发error事件
在onerror事件处理程序中,会传入三个参数:错误消息、发生错误的URL和行号
onerror事件处理程序需要使用DOM Level 0技术来指定
可以在onerror事件处理程序中返回false来阻止浏览器默认报告错误的行为
这代表浏览器错误最后一道防线,如果使用try/catch得当,浏览器应该不会到达这个事件
不同浏览器中对于这个事件的处理机制不一样(红宝书p682)
图片也支持error事件,在图片src属性中的URL没有返回可识别的图片格式时触发
错误处理策略
作为开发者,应当知晓自己的代码在什么情况下会失败,以及失败会导致什么结果;另外还要有一个系统来跟踪这些问题
识别错误
因为js是松散类型的,不会验证函数参数,所以有些代码只有运行起来才能知道错误;通常也要注意3类错误:
类型转换错误
数据类型错误
通信错误
1、静态代码分析器
在代码构建流程中添加静态代码分析或检查器(linter),可以预先发现非常多错误
常用的工具是JSHint、JSLint、Google Closure、TypeScript;静态代码分析器要求使用类型、函数签名以及其他指令来注解js;分析器会通过注解和js代码的各个部分,对实际运行时可能出现的潜在不兼容问题给出提醒
2、类型转换错误
通过使用严格不相等可以预防这种错误;if语句中如果使用typeof xxx === "string"
这种强制比较,也可以预防这种错误
3、数据类型错误
在使用某数据类型时,先判断其类型正确,再使用,可以防止类型错误报错
4、通信错误
对于查询字符串,应该使用encodeURIComponent()编码
区分重大与非重大错误
具有一个或多个特性的错误属于非重大错误:
不会影响用户的主要任务
只会影响页面中某个部分
可以恢复
重复操作可能成功
本质上,不需要担心非重大错误
重大错误具备如下特性:
应用程序绝对无法继续运行
错误严重影响了用户的主要目标
会导致其他错误发生
如果发生重大错误,必须要通知用户,告知用户不能使用或者需要刷新页面等
把错误记录到服务器中
Web应用程序开发中的一个常见的做法是建立中心化的错误日志存储和跟踪系统
function logError(sev, msg) {
let img = new Image(),
encodedSev = encodeURIComponent(sev),
encodedMsg = encodeURIComponent(msg);
img.src = 'log.php?sev=${encodedSev}&msg=${encodedMsg}';
}
这个函数接收两个参数:严重程度、错误消息
在catch中使用logError函数向服务器发送错误日志;这里使用Image对象发送,因为其灵活性:所有浏览器都支持、不受跨域规则限制、记录错误的过程很少出错
调试技术
把消息记录到控制台
所有主流浏览器都有js控制台,通过console对象直接把js消息写入控制台,其包含如下方法:
error(message):在控制台中记录错误信息
info(message):在控制台中记录信息性内容
log(message):在控制台中记录常规消息
warn(message):在控制台中记录警告消息
理解控制台运行时
浏览器控制台是个读取-求值-打印-循环,与页面js运行时并发
在Element元素标签页中选中一个元素,可以在控制台使用$0来访问
使用JavaScript调试器
ECMAScript 5.1规范定义了debugger关键字,用于调用可能存在的调试功能;这个功能等同于断点
浏览器也支持在开发者工具的源代码页中选择希望设置断点的代码行来手动设置断点;这样子设置的断点与使用debugger关键字一样,只是不会在不同浏览器会话中保存
在页面中打印消息
可以把消息写到页面某个指定区域
补充控制台方法
可以自定义console.log方法,赋给其一个自定义函数;但是这种方法在页面刷新后会失效
抛出错误
手动检查错误并且抛出具体信息,会比浏览器默认抛出的错误要更具体
在大型应用程序中,通常使用assert()函数抛出错误
//目标函数
function test(num1, num2) {
assert(typeof num1 == "number" && typeof num2 == "number", "test(): arguments wrong");
return num1 / num2;
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
旧版IE常见错误
IE曾是最难调试的JavaScript错误的浏览器之一,该方案可以了解,因为IE现如今已经不受支持
无效字符
JavaScript代码必须由特定字符构成;在检测到JavaScript文件中存在无效字符时,IE会抛出“invalid character”错误
例如看起来像减号但是又不是减号的字符(Unicode值为/u2013),FIrefox、Safari、Opera都会报错
未找到成员
旧版IE中所有DOM对象都是用COM对象实现的,并非原生JavaScript对象;在涉及垃圾回收时,这可能会导致很多奇怪的行为
例如,event对象,IE的event是作为window的一个属性存在的,事件完成后就会被销毁,在之后的闭包中使用,尝试给event对象赋值就会发生错误
未知运行时错误
使用innerHTML和outerHTML属性以下面一种方式添加HTML时会发生运行时错误:将块级元素插入行内元素、在表格任何部分访问了其中一个属性
语法错误
通常IE报告语法错误,原因是很清楚的
但是当网页中引入了一个外部js文件由于某种原因返回了非js代码,IE会抛出错误,报告该语法错误发生在脚本第一行的第一个字符;Opera和Safari也会报告语法错误,但是会报告是引用文件不当导致的问题;Firefox会忽略作为js引用非js文件导致的解析错误
系统找不到指定资源
还有一个可能最没用的消息:“The system cannot locate the resource specified”(系统找不到指定资源);这个错误会在js发送请求而URL长度超过了IE允许的最大URL长度(2083个字符)时发生
IE对URL路径还有2048个字符的限制