第 21 章 错误处理与调试
21.1 浏览器错误报告
21.1.1 桌面控制台
所有现代桌面浏览器都会通过控制台暴露错误。
21.1.2 移动控制台
Chrome 移动版和 Safari 的 iOS 版内置了实用工具,支持将设备连接到宿主操作系统中相同的浏览器。然后,就可以在对应的桌面浏览器中查看错误了。
21.2 错误处理
21.2.1 try/catch 语句
try {
window.someNonexistentFunction()
} catch (error) {
console.log(error.message)
}
如果 try
块中有代码发生错误,代码会立即退出执行,并跳到 catch
块中。catch
块此时接收到一个对象,该对象包含发生错误的相关信息。与其他语言不同,即使在 catch
块中不使用错误对象,也必须为它定义名称。错误对象中暴露的实际信息因浏览器而异,但至少包含保存错误消息的 message
属性。ECMA-262 也指定了定义错误类型的 name
属性,目前所有浏览器中都有这个属性。
1.finally 子句
try/catch
语句中可选的 finally
子句始终运行。如果 try
块中的代码运行完,则接着执行 finally
块中的代码。如果出错并执行 catch
块中的代码,则 finally
块中的代码仍执行。try
或 catch
块无法阻止 finally
块执行,包括 return
语句。
2.错误类型
ECMA-262 定义了以下 8 种错误类型:
❑ Error
❑ InternalError
❑ EvalError
❑ RangeError
❑ ReferenceError
❑ SyntaxError
❑ TypeError
❑ URIError
Error 是基类型,其他错误类型继承该类型。因此,所有错误类型都共享相同的属性(所有错误对象上的方法都是这个默认类型定义的方法)。浏览器很少会抛出 Error 类型的错误,该类型主要用于开发者抛出自定义错误。
InternalError 类型的错误会在底层 JavaScript 引擎抛出异常时由浏览器抛出。例如,递归过多导致了栈溢出。这个类型并不是代码中通常要处理的错误,如果真发生了这种错误,很可能代码哪里弄错了或者有危险了。
EvalError 类型的错误会在使用 eval()函数发生异常时抛出。ECMA-262 规定,“如果 eval 属性没有被直接调用(即没有将其名称作为一个 Identifier,也就是 CallExpression 中的 MemberExpression),或者如果 eval 属性被赋值”,就会抛出该错误。基本上,只要不把 eval()当成函数调用就会报告该错误:
new eval() // 抛出EvalError
eval = foo // 抛出EvalError
RangeError 错误会在数值越界时抛出。
ReferenceError 会在找不到对象时发生。(这就是著名的"object expected"浏览器错误的原因。)这种错误经常是由访问不存在的变量而导致的
let obj = x // 在x没有声明时会抛出ReferenceError
SyntaxError 经常在给 eval()传入的字符串包含 JavaScript 语法错误时发生。
TypeError 在 JavaScript 中很常见,主要发生在变量不是预期类型,或者访问不存在的方法时。很多原因可能导致这种错误,尤其是在使用类型特定的操作而变量类型不对时。
let o = new 10() // 抛出TypeError
console.log('name' in true) // 抛出TypeError
Function.prototype.toString.call('name') // 抛出TypeError
URIError,只会在使用 encodeURI()
或 decodeURI()
但传入了格式错误的 URI 时发生。
3.try/catch 的用法
21.2.2 抛出错误
与 try/catch
语句对应的一个机制是 throw
操作符,用于在任何时候抛出自定义错误。throw
操作符必须有一个值,但值的类型不限。
throw 12345
throw 'Hello world! '
throw true
throw { name: 'JavaScript' }
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.")
class CustomError extends Error {
constructor(message) {
super(message)
this.name = 'CustomError'
this.message = message
}
}
throw new CustomError('My message')
创建自定义错误类型时,需要提供 name
属性和 message
属性。
1.何时抛出错误
2.抛出错误与 try/catch
捕获错误的目的是阻止浏览器以其默认方式响应;抛出错误的目的是为错误提供有关其发生原因的说明。
21.2.3 error 事件
任何没有被 try/catch
语句处理的错误都会在 window
对象上触发 error
事件。
window.onerror = (message, url, line) => {
console.log(message)
return false // 可以返回 false 来阻止浏览器默认报告错误的行为
}
图片也支持 error
事件。任何时候,如果图片 src
属性中的 URL 没有返回可识别的图片格式,就会触发 error
事件。这个事件遵循 DOM 格式,返回一个以图片为目标的 event
对象。
const image = new Image()
image.addEventListener('load', (event) => {
console.log('Image loaded! ')
})
image.addEventListener('error', (event) => {
console.log('Image not loaded! ')
})
image.src = 'doesnotexist.gif' // 不存在,资源会加载失败
21.2.4 错误处理策略
21.2.5 识别错误
错误处理非常重要的部分是首先识别错误可能会在代码中的什么地方发生。因为 JavaScript 是松散类型的,不会验证函数参数,所以很多错误只有在代码真正运行起来时才会出现。通常,需要注意 3 类错误:
❑ 类型转换错误
❑ 数据类型错误
❑ 通信错误
1.静态代码分析器
2.类型转换错误
3.数据类型错误
4.通信错误
第一种错误是 URL 格式或发送数据的格式不正确。通常,在把数据发送到服务器之前没有用 encodeURIComponent()
编码,会导致这种错误。
在服务器响应非预期值时也会发生通信错误。在动态加载脚本或样式时,请求的资源有可能不可用。有些浏览器在没有返回预期资源时会静默失败,而其他浏览器则会报告错误。不过,在动态加载资源的情况下出错,是不太好做错误处理的。有时候,使用 Ajax 通信可能会提供关于错误条件的更多信息。
21.2.6 区分重大与非重大错误
21.2.7 把错误记录到服务器中
21.3 调试技术
21.3.1 把消息记录到控制台
21.3.2 理解控制台运行时
21.3.3 使用 JavaScript 调试器
debugger
21.3.4 在页面中打印消息
function log(message) {
// 这个函数的词法作用域会使用这个实例
// 而不是window.console
const console = document.getElementById('debuginfo')
if (console === null) {
console = document.createElement('div')
console.id = 'debuginfo'
console.style.background = '#dedede'
console.style.border = '1px solid silver'
console.style.padding = '5px'
console.style.width = '400px'
console.style.position = 'absolute'
console.style.right = '0px'
console.style.top = '0px'
document.body.appendChild(console)
}
console.innerHTML += '<p> ${message}</p>'
}
21.3.5 补充控制台方法
console
是一个全局对象,可以为这个对象添加方法,也可以用自定义的函数重写已有的方法,这样无论在哪里用到的日志打印方法,都会按照自定义的方式行事。
21.3.6 抛出错误
自定义错误通常使用 assert()
函数抛出错误。这个函数接收一个应该为 true
的条件,并在条件为 false
时抛出错误。
function assert(condition, message) {
if (!condition) {
throw new Error(message)
}
}
function divide(num1, num2) {
assert(typeofnum1 == 'number' && typeofnum2 == 'number', 'divide(): Both arguments must be numbers.')
return num1 / num2
}