JavaScript 中的异常处理
考虑到 JS 中的错误可比服务器端的代码产生的错误要多得多,并且还难以发现及修正,所以 JS 代码必须有异常处理以及全局一场处理。
try {
//这段代码从上往下运行,其中任何一个语句抛出异常该代码块就结束运行}
catch (e) {
// 如果try代码块中抛出了异常,catch代码块中的代码就会被执行。
//e是一个局部变量,用来指向Error对象或者其他抛出的对象
}
finally {//无论try中代码是否有异常抛出(甚至是try代码块中有return语句),finally代码块中始终会被执行。
}
一:Error属性
Error有两个基本的属性 name 和 message 。message用来表示异常的详细信息。而name指的是Error对象的构造函数。此外,不同的js引擎对Error还各自提供了一些扩展,例如mozilla提供了fileName(异常出现的文件名称)和linenumber(异常出现的行号)的扩展,而IE提供了number(错误号)的支持。不过name和message是两个基本的属性,在firefox和ie中都能够支持。
二:Error类型
Javascript中Error还有几个子类EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError,
EvalError:错误发生在eval()中
SyntaxError:语法错误,错误发生在eval()中,因为其它点发生SyntaxError会无法通过解释器RangeError:数值超出范围
ReferenceError:引用不可用
TypeError:变量类型不是预期的
URIError:错误发生在encodeURI()或decodeURI()中
三:全局异常处理
javascript的window对象有一个特别的属性onerror,如果你将某个function赋值给window的onerror属性,那么但凡这个window中有javascript错误出现,该function都会被调用,也就是说这个function会成为这个window的错误处理句柄。不过,需要注意的是,我们需要让下面这段代码成为文件中的第一行代码:
window.onerror = function(msg, url, line) {
return true;}
onerror句柄会3个参数分别是错误信息提示,产生错误的javascript的document ulr,错误出现的行号。
onerroe句柄的返回值也很重要,如果句柄返回true,表示浏览器无需在对该错误做额外的处理,也就是说浏览器不需要再显示错误信息。而如果返回的是false,浏览器还是会提示错误信息。
一篇很好的参考文章(http://www.cnblogs.com/cathsfz/p/how-to-capture-and-analyze-javascript-error.html):
前端工程师都知道 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 服务,但如果要弄明白其中的细节和原理还是必须自己亲手做一次。
JavaScript 中的异常处理的更多相关文章
- 【转】详解JavaScript中的异常处理方法
有三种类型的编程错误:(1)语法错误和(2)运行时错误(3)逻辑错误:语法错误: 语法错误,也被称为解析错误,在编译时进行传统的编程语言,并出现在JavaScript解释时. 例如,下面一行将导致一个 ...
- JavaScript中的try...catch和异常处理
在JavaScript可以使用try...catch来进行异常处理.例如: try { foo.bar();} catch (e) { alert(e.name + ": " + ...
- 【repost】JS中的异常处理方法分享
我们在编写js过程中,难免会遇到一些代码错误问题,需要找出来,有些时候怕因为js问题导致用户体验差,这里给出一些解决方法 js容错语句,就是js出错也不提示错误(防止浏览器右下角有个黄色的三角符号,要 ...
- js中的异常处理try...catch使用介绍
在JavaScript可以使用try...catch来进行异常处理. 例如: try { foo.bar();} catch (e) { alert(e.name + ": " + ...
- AOP 在javascript 中的使用
AOP(Aspect Oriented Programming) 意为面向切面编程 可以在不修改原有代码的情况下增加新功能,利用AOP可以对业务逻辑各个部分进行隔离,从而使得业务逻辑各部分的耦合度降低 ...
- JavaScript中错误正确处理方式,你用对了吗?
JavaScript的事件驱动范式增添了丰富的语言,也是让使用JavaScript编程变得更加多样化.如果将浏览器设想为JavaScript的事件驱动工具,那么当错误发生时,某个事件就会被抛出.理论上 ...
- 【javaScript基础】异常处理
理解异常在javaScript面向对象编程是非常重要的,异常是一种非常强大的处理错误的方式. 错误处理 首先我们来看一个有问题的代码: nonexistant(); ...
- javascript中的正确错误处理------------引用
JavaScript的事件驱动机制让JavaScript更加丰富,浏览器好比就是一个事件驱动的机器,错误也是一种事件.当一个错误发生时,一个事件就在某个点抛出. 解释起来就是,当发生错误时,JavaS ...
- 掌握JavaScript中的Promise,实现异步编程
事件循环 基本介绍 JavaScript是一门单线程的编程语言,所以没有真正意义上的并行特性. 为了协调事件处理.页面交互.脚本调用.UI渲染.网络请求等行为对主线程造成的影响,事件循环(event ...
随机推荐
- Git(二)使用git管理文件版本(TortoiseGit )
一.创建版本库 什么是版本库呢?版本库又名仓库,英文名repository,你可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改.删除,Git都能跟踪,以便任何时刻都 ...
- Spring中AOP实现
1.什么是SpringAOP 什么是aop:Aspect Oriented Programming的缩写,面向切面编程,通过预编译和动态代理实现程序功能的 统一维护的一种技术 主要功能:日志记录,性能 ...
- 自然语言处理---用隐马尔科夫模型(HMM)实现词性标注---1998年1月份人民日报语料---learn---test---evaluation---Demo---java实现
先放上一张Demo的测试图 测试的句子及每个分词的词性标注为: 目前/t 这/rzv 条/q 高速公路/n 之间/f 的/ude1 路段/n 已/d 紧急/a 封闭/v ./w 需要基础知识 HM ...
- cv2 与 matplotlib 的 Bug 记录
cv2 的 imread 无法读取中文路径 解决方案: img = cv2.imdecode(np.fromfile(image_path,dtype=np.uint8),cv2.IMREAD_COL ...
- git初级浅入其常用操作
1. git init 我们从初始化一个仓库开始,通过此命令可以初始化一个仓库 git init 首先我们在当前目录下创建一个目录pratice和一个文件test.js mkdir pratice c ...
- 求任意多边形面积 python实现
数学解决方法: 多边形外选取一点,连接各点构成三角形,计算求和...... 详细链接 http://blog.csdn.net/hemmingway/article/details/7814494 ...
- 循序渐进学.Net Core Web Api开发系列【15】:应用安全
系列目录 循序渐进学.Net Core Web Api开发系列目录 本系列涉及到的源码下载地址:https://github.com/seabluescn/Blog_WebApi 一.概述 本篇介绍W ...
- ubuntu下安装flash player,浏览器观看视频,本人ubuntu版本14.04
首先去官网下载flash player安装包:flash_player_npapi_linux.x86_64,下载地址:https://get.adobe.com/cn/flashplayer/ 解压 ...
- WEP保护帧移除工具airdecloak-ng
WEP保护帧移除工具airdecloak-ng 为了防止WEP加密数据被破解,WIPS(无线入侵防御系统)会发送WEP保护帧.攻击者抓取WEP数据包时,也会获取这一类包,导致破解失败.aircra ...
- 使用Python中的HTMLParser、cookielib抓取和解析网页、从HTML文档中提取链接、图像、文本、Cookies(二)(转)
对搜索引擎.文件索引.文档转换.数据检索.站点备份或迁移等应用程序来说,经常用到对网页(即HTML文件)的解析处理.事实上,通过 Python语言提供的各种模块,我们无需借助Web服务器或者Web浏览 ...