第21章 错误处理与调试
# 浏览器错误报告
可以通过浏览器控制台的 Console 查看日志和可能发生的错误。
# 错误处理
# try/catch
js 使用 try/catch 语句来处理异常。
try {
// 可能出错的代码
window.someFunction()
} catch (error) {
// 捕获错误,所有浏览器都支持 message 属性
console.log(error.message)
}
2
3
4
5
6
7
try 或 catch 块无法阻止 finally 块执行,包括 return 语句。
function test() {
try {
return 1
} catch (error) {
return 2
} finally {
return 3
}
}
console.log(test()) // 3
2
3
4
5
6
7
8
9
10
11
# 错误类型
- Error:基类型,其他错误类型继承。
- InternalError:在底层 JavaScript 引擎抛出异常时由浏览器抛出。
- EvalError:在使用 eval()函数发生异常时抛出。
- RangeError:在数值越界时抛出。
- ReferenceError:在找不到对象时发生,比如访问不存在的变量。
- TypeError:变量不是预期类型,或者访问不存在的方法是发生。
- URIError:encodeURI()或 decodeURI 调用出错时发生。
可以用 instanceof 来确定错误类型,比如:
try {
doSomeFunction()
} catch (error) {
if (error instanceof TypeError) {
console.log('TypeError')
}
// ...
}
2
3
4
5
6
7
8
# try/catch 用法
当 try/catch 中发生错误时,浏览器认为错误被处理了,就不会报告了(window.onerror 不能触发)。
window.onerror = function(e) {
console.log('error', e)
}
try {
doSomeFunction()
} catch (error) {
console.log('try-error', error.message) // 错误已自行捕获,不会触发window.onerror
}
// doSomeFunction() // 会触发window.onerror
2
3
4
5
6
7
8
9
try/catch 语句最好用在自己无法控制的代码中,比如第三方库,因为一般不会去修改第三方库,但也许其内部会意外抛出错误。
# 抛出错误
通过 throw 操作符来抛出错误,抛出的值类型不限。
try {
// throw 1
// throw 'hello'
// throw [1, 2]
// throw true
throw new Error('something wrong')
} catch (error) {
console.log(error)
}
2
3
4
5
6
7
8
9
# 何时抛出与 try/catch
捕获错误的目的:阻止浏览器以其默认方式响应,说白了就是吃掉错误,不让浏览器发现。
抛出错误的目的:为错误提供有关其发生原因的说明,说白了是禁止代码继续执行,并报告错误行为。
# onerror 事件
浏览器相关有 2 类 error 事件可以触发:
- window.onerror:没有被 try/catch 处理的错误,都会是 window 上触发 error 事件。尽量不要返回 false 来阻止报告错误的默认行为。
- img.onerror:图片加载出错时触发。
# 识别错误
js 是弱类型语言,很多错误只有在代码运行时才会出现。常见的错误类型有 3 类:
类型转换错误
即隐式转换导致的错误,比如
==
、if
、for
、while
等控制语句中会进行类型转换。解决方式是,尽可能使用
===
,以及 if 判断时,注意非空判断,如undefined
,null
,0
等均会转为 false,需要注意。数据类型错误
变量和函数参数的数据类型可能会变,但使用方不做校验的话,可能导致错误。
解决方式是,使用前通过类型检查保证安全,如
typeof
,Array.isArray
。通信错误
ajax 通信过程中出现的错误,如 URL 格式不对,需要使用
encodeURIComponent
编码。
# 区分错误程度
p0 级错误:
- 应用程序无法继续运行
- 严重影响用户体验
- 导致其他模块发生错误
看下这段代码,是遍历所有模块,调用其初始化方法。
for (let mod of mods) {
mod.init() // 可能的重大错误
}
2
3
问题在于,代码是同步执行的,如果其中一个模块初始化失败,抛出错误,将导致剩余的模块无法初始化。
这属于 p0 级错误。
解决方式如下:
for (let mod of mods) {
try {
mod.init()
} catch (e) {
// 处理错误
}
}
2
3
4
5
6
7
# 错误记录
不只是后端服务需要日志和错误记录,前端也需要。前端是直接面对用户的,如果程序异常导致用户无法继续浏览和交互,就需要相应的机制进行记录。常用的方式有:
- ajax 请求
- 图片上报
- 无兼容性问题
- 不受跨域限制
# 调试技术
# console
使用 console.log 进行消息记录,可以在控制台查看。
除了 log,还有 error,info,warn 等方法。
浏览器控制台是 REPL(read-eval-print-loop),在控制台中执行的命令可以像页面级 JS 一样访问全局和各种 API,并且修改、对象和回调都会保留在 DOM 和运行时中。
选择元素后,该元素会临时标记为$0,在控制台可以使用$0 引用该节点的 js 实例,可以读取属性(如$0.clientHeight)和调用成员方法(如$0.remove())。
# debugger
可以在源码中插入 debugger 关键字,js 运行到该行时会停在该断点。
也支持在浏览器中手动设置断点进行调试。
# 页面中打印
将调试信息在 dom 中显示。
仅开发调试阶段使用,开发完毕要清除。
# 抛出错误
预先检查错误可能的原因,方便使用者定位问题,可以封装一个 assert 方法来断言。
function assert(condition, message) {
if (!condition) {
throw new Error(message)
}
}
function divide(num1, num2) {
assert(typeof num1 === 'number' && typeof num2 === 'number', 'arguments must be number')
return num1 / num2
}
console.log(divide('1', 3))
2
3
4
5
6
7
8
9
10
11