# 错误处理与调试
# 1、浏览器错误报告
默认情况下,所有浏览器都会隐藏错误信息。
# 1.1 桌面控制台
所有现代桌面浏览器都会通过控制台暴露错误。这些错误可以显示在开发者工具内嵌的控制台中。
可能最简单的查看错误的方式就是在页面上单击鼠标右键,然后在上下文菜单中选择 Inspect(检查)或 Inspect Element(检查元素),然后再单击 Console(控制台)选项卡。
# 1.2 移动控制台
移动浏览器不会直接在设备上提供控制台界面。
Chrome 移动版和 Safari 的 iOS 版内置了实用工具,支持将设备连接到宿主操作系统中相同的浏览器。
# 2、错误处理
# 2.1 try/catch 语句
try {
// 可能出错的代码
} catch (error) {
// 出错时要做什么
console.log(error.message);
} finally {
// 执行操作
}
2
3
4
5
6
7
8
# 2.2 错误类型
ECMA-262 定义了以下 8 种错误类型:
- Error 是基类型,其他错误类型继承该类型。
- InternalError 类型的错误会在底层 JavaScript 引擎抛出异常时由浏览器抛出。
- EvalError 在使用 eval() 函数发生异常时抛出。
- RangeError 错误会在数值越界时抛出。
- ReferenceError 会在找不到对象时发生。
- SyntaxError 经常在给 eval() 传入的字符串包含 JavaScript 语法错误时发生
- TypeError 在变量不是预期类型,或者访问不存在的方法时。
- URIError 在使用 encodeURI() 或 decodeURI() 但传入了格式错误的 URI 时发生
# 2.3 抛出错误
与 try/catch 语句对应的一个机制是 throw 操作符,用于在任何时候抛出自定义错误。
throw 操作符必须有一个值,但值的类型不限。
throw 12345;
throw "Hello world!";
throw true;
throw { name: "JavaScript" };
2
3
4
使用 throw 操作符时,代码立即停止执行,除非 try/catch 语句捕获了抛出的值。
throw new Error("Something bad happened.");
throw new SyntaxError("I don't like your syntax.");
throw new InternalError("I can't do that, Dave.");
throw new TypeError("What type of variable do you take me for?");
throw new RangeError("Sorry, you just don't have the range.");
throw new EvalError("That doesn't evaluate.");
throw new URIError("Uri, is that you?");
throw new ReferenceError("You didn't cite your references properly.");
2
3
4
5
6
7
8
自定义错误类型:
class CustomError extends Error {
constructor(message) {
super(message);
this.name = "CustomError";
this.message = message;
}
}
throw new CustomError("My message");
2
3
4
5
6
7
8
# 2.4 error 事件
window.onerror = (message, url, line) => {
console.log(message);
return false; // 返回 false 来阻止浏览器默认报告错误的行为
};
2
3
4
# 2.5 识别错误
因为 JavaScript 是松散类型的,不会验证函数参数,所以很多错误只有在代码真正运行起来时才会出现。
通常,需要注意 3 类错误:
- 类型转换错误
- 数据类型错误
- 通信错误
(1)静态代码分析器
常用的静态分析工具 是 JSHint、JSLint、Google Closure 和 TypeScript。
(2)类型转换错误
类型转换错误的主要原因是使用了会自动改变某个值的数据类型的操作符或语言构造。使用等于(==)或不等于(!=)操作符,以及在 if、for 或 while 等流控制语句中使用非布尔值,经常会导致类型转换错误。
大多数情况下,最好使用严格相等(===)和严格不相等(!==)操作符来避免类型转换。
(3)数据类型错误
因为 JavaScript 是松散类型的,所以变量和函数参数都不能保证会使用正确的数据类型。开发者需要自己检查数据类型,确保不会发生错误。
// 不安全的函数,任何非字符串值都会导致错误
function getQueryString(url) {
const pos = url.indexOf("?");
if (pos > -1){
return url.substring(pos +1);
}
return "";
}
function getQueryString(url) {
if (typeof url === "string") { // 通过类型检查保证安全
let pos = url.indexOf("?");
if (pos > -1) {
return url.substring(pos +1);
}
}
return "";
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(4)通信错误
随着 Ajax 编程的出现,Web 应用程序在运行期间动态加载数据和功能成为常见的情形。JavaScript 和服务器之间的通信也会出现错误。
第一种错误是 URL 格式或发送数据的格式不正确。通常,在把数据发送到服务器之前没有用 encodeURIComponent() 编码,会导致这种错误。
对于查询字符串,应该都要通过 encodeURIComponent() 编码。
function addQueryStringArg(url, name, value) {
if (url.indexOf("?") == -1){
url += "?";
} else {
url += "&";
}
url += '${encodeURIComponent(name)=${encodeURIComponent(value)}';
return url;
}
2
3
4
5
6
7
8
9
# 2.6 把错误记录到服务器中
function logError(sev, msg) {
let img = new Image(),
encodedSev = encodeURIComponent(sev),
encodedMsg = encodeURIComponent(msg);
img.src = 'log.php?sev=${encodedSev}&msg=${encodedMsg}';
}
2
3
4
5
6
# 3、调试技术
# 3.1 把消息记录到控制台
通过 console 对象直接把 JavaScript 消息写入控制台,这个对象包含如下方法。
- error(message):在控制台中记录错误消息。
- info(message):在控制台中记录信息性内容。
- log(message):在控制台记录常规消息。
- warn(message):在控制台中记录警告消息。
打印日志也可以随意输出任意多个参数并检查对象实例(警告框只能将对象序列化为一个字符串再展示出来,因此经常会看到 Object[Object]。
# 3.2 理解控制台运行时
浏览器控制台是个读取-求值-打印-循环(REPL,read-eval-print-loop),与页面的 JavaScript 运行时并发。这个运行时就像浏览器对新出现在 DOM 中的<script>
标签求值一样。在控制台中执行的命令可以像页面级 JavaScript 一样访问全局和各种 API。控制台中可以执行任意数量的代码,与它可能会阻塞的任何页面级代码一样。修改、对象和回调都会保留在 DOM 和运行时中。
# 3.3 使用 JavaScript 调试器
function pauseExecution() {
console.log("Will print before breakpoint");
debugger;
console.log("Will not print until breakpoint continues");
}
2
3
4
5
在运行时碰到这个关键字时,所有主流浏览器都会打开开发者工具面板,并在指定位置显示断点。
# 3.4 在页面中打印消息
另一种常见的打印调试消息的方式是把消息写到页面中指定的区域。
与在控制台输出消息一样,在页面中输入消息的代码也需要从生产环境中删除。
# 3.5 补充控制台方法
// 把所有参数拼接为一个字符串,然后打印出结果
console.log = function() {
// 'arguments'并没有 join 方法,这里先把它转换为数组
const args = Array.prototype.slice.call(arguments);
console.log(args.join(', '));
}
2
3
4
5
6