全面了解javascript中的错误处理机制


Posted in Javascript onJuly 18, 2016

前面的话

错误处理对于web应用程序开发至关重要,不能提前预测到可能发生的错误,不能提前采取恢复策略,可能导致较差的用户体验。由于任何javascript错误都可能导致网页无法使用,因此作为开发人员,必须要知道何时可能出错,为什么会出错,以及会出什么错。本文将详细介绍javascript中的错误处理机制

error对象  

error对象是包含错误信息的对象,是javascript的原生对象。当代码解析或运行时发生错误,javascript引擎就会自动产生并抛出一个error对象的实例,然后整个程序就中断在发生错误的地方

console.log(t);//Uncaught ReferenceError: t is not defined

ECMA-262规定了error对象包括两个属性:message和name。message属性保存着错误信息,而name属性保存错误类型

//一般地,使用try-catch语句来捕获错误
try{
  t;
}catch(ex){
  console.log(ex.message);//t is not defined 
  console.log(ex.name);//ReferenceError
}

浏览器还对error对象的属性做了扩展,添加了其他相关信息。其中各浏览器厂商实现最多的是stack属性,它表示栈跟踪信息(safari不支持)

try{
  t;
}catch(ex){
  console.log(ex.stack);//@file:///D:/wamp/www/form.html:12:2
}

当然,可以使用error()构造函数来创建错误对象。如果指定message参数,则该error对象将把它用做它的message属性;若不指定,它将使用一个预定义的默认字符串作为该属性的值

new Error();
new Error(message);  
//一般地,使用throw语句来抛出错误
throw new Error('test');
//Uncaught Error: test
throw new Error();//Uncaught Error
function UserError(message) {
  this.message = message;
  this.name = "UserError";
}
UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
throw new UserError("errorMessage");//Uncaught UserError: errorMessage

当不使用new操作符,直接将Error()构造函数像一个函数一样调用时,它的行为和带new操作符调用时一样

Error();
Error(message);  

throw Error('test');//Uncaught Error: test
throw Error();//Uncaught Error

error对象有一个toString()方法,返回'Error:'+ error对象的message属性

var test = new Error('testError');
console.log(test.toString());//'Error: testError'

error类型  

执行代码期间可能会发生的错误有多种类型。每种错误都有对应的错误类型,而当错误发生时,就会抛出相应类型的错误对象。ECMA-262定义了下列7种错误类型:

Error
EvalError(eval错误)
RangeError(范围错误)
ReferenceError(引用错误)
SyntaxError(语法错误)
TypeError(类型错误)
URIError(URI错误)

其中,Error是基类型,其他错误类型都继承自该类型。因此,所有错误类型共享了一组相同的属性。Error类型的错误很少见,如果有也是浏览器抛出的;这个基类型的主要目的是供开发人员抛出自定义错误

【EvalError(eval错误)】

eval函数没有被正确执行时,会抛出EvalError错误。该错误类型已经不再在ES5中出现了,只是为了保证与以前代码兼容,才继续保留

【RangeError(范围错误)】

RangeError类型的错误会在一个值超出相应范围时触发,主要包括超出数组长度范围以及超出数字取值范围等

new Array(-1);//Uncaught RangeError: Invalid array length
new Array(Number.MAX_VALUE);//Uncaught RangeError: Invalid array length

(1234).toExponential(21);//Uncaught RangeError: toExponential() argument must be between 0 and 20
(1234).toExponential(-1);////Uncaught RangeError: toExponential() argument must be between 0 and 20

【ReferenceError(引用错误)】

引用一个不存在的变量或左值(lvalue)类型错误时,会触发ReferenceError(引用错误)

a;//Uncaught ReferenceError: a is not defined
1++;//Uncaught ReferenceError: Invalid left-hand side expression in postfix operation

【SyntaxError(语法错误)】

当不符合语法规则时,会抛出SyntaxError(语法错误)

//变量名错误
var 1a;//Uncaught SyntaxError: Unexpected number

// 缺少括号
console.log 'hello');//Uncaught SyntaxError: Unexpected string

【TypeError(类型错误)】

在变量中保存着意外的类型时,或者在访问不存在的方法时,都会导致TypeError类型错误。错误的原因虽然多种多样,但归根结底还是由于在执行特定类型的操作时,变量的类型并不符合要求所致

var o = new 10;//Uncaught TypeError: 10 is not a constructor
alert('name' in true);//Uncaught TypeError: Cannot use 'in' operator to search for 'name' in true
Function.prototype.toString.call('name');//Uncaught TypeError: Function.prototype.toString is not generic

【URIError(URI错误)】

URIError是URI相关函数的参数不正确时抛出的错误,主要涉及encodeURI()、decodeURI()、encodeURIComponent()、decodeURIComponent()、escape()和unescape()这六个函数

decodeURI('%2');// URIError: URI malformed

error事件

任何没有通过try-catch处理的错误都会触发window对象的error事件

error事件可以接收三个参数:错误消息、错误所在的URL和行号。多数情况下,只有错误消息有用,因为URL只是给出了文档的位置,而行号所指的代码行既可能出自嵌入的JavaScript代码,也可能出自外部的文件

要指定onerror事件处理程序,可以使用DOM0级技术,也可以使用DOM2级事件的标准格式

//DOM0级
window.onerror = function(message,url,line){
  alert(message);
}
//DOM2级
window.addEventListener("error",function(message,url,line){
  alert(message);
});

浏览器是否显示标准的错误消息,取决于onerror的返回值。如果返回值为false,则在控制台中显示错误消息;如果返回值为true,则不显示

//控制台显示错误消息
window.onerror = function(message,url,line){
  alert(message);
  return false;
}
a;

//控制台不显示错误消息
window.onerror = function(message,url,line){
  alert(message);
  return true;
}
a;

这个事件处理程序是避免浏览器报告错误的最后一道防线。理想情况下,只要可能就不应该使用它。只要能够适当地使用try-catch语句,就不会有错误交给浏览器,也就不会触发error事件

图像也支持error事件。只要图像的src特性中的URL不能返回可以被识别的图像格式,就会触发error事件。此时的error事件遵循DOM格式,会返回一个以图像为目标的event对象

加载图像失败时会显示一个警告框。发生error事件时,图像下载过程已经结束,也就是不能再重新下载了

var image = new Image();
image.src = 'smilex.gif';
image.onerror = function(e){
  console.log(e);
}

throw语句与抛出错误  

throw语句用于抛出错误。抛出错误时,必须要给throw语句指定一个值,这个值是什么类型,没有要求

[注意]抛出错误的过程是阻塞的,后续代码将不会执行

throw 12345;
throw 'hello world';
throw true;
throw {name: 'javascript'};

可以使用throw语句手动抛出一个Error对象

throw new Error('something bad happened');

throw new SyntaxError('I don\'t like your syntax.');
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');

利用原型链还可以通过继承Error来创建自定义错误类型(原型链在第6章中介绍)。此时,需要为新创建的错误类型指定name和message属性

浏览器对待继承自Error的自定义错误类型,就像对待其他错误类型一样。如果要捕获自己抛出的错误并且把它与浏览器错误区别对待的话,创建自定义错误是很有用的

function CustomError(message){
  this.name = 'CustomError';
  this.message = message;
}
CustomError.prototype = new Error();
throw new CustomError('my message');

在遇到throw语句时,代码会立即停止执行。仅当有try-catch语句捕获到被抛出的值时,代码才会继续执行

更详细的解释为:当抛出异常时,javascript解释器会立即停止当前正在执行的逻辑,并跳转到就近的异常处理程序。异常处理程序是用try-catch语句的catch从句编写的。如果抛出异常的代码块没有一条相关联的catch从句,解释器会检查更高层的闭合代码块,看它是否有相关联的异常处理程序。以此类推,直到找到一个异常处理程序为止。如果抛出异常的函数没有处理它的try-catch语句,异常将向上传播到调用该函数的代码。这样的话,异常就会沿着javascript方法的词法结构和调用栈向上传播。如果没有找到任何异常处理程序,javascript将把异常当成程序错误来处理,并报告给用户

try catch语句与捕获错误  

ECMA-262第3版引入了try-catch语句,作为JavaScript中处理异常的一种标准方式,用于捕获和处理错误

其中,try从句定义了需要处理的异常所在的代码块。catch从句跟随在try从句之后,当try块内某处发生了异常时,调用catch内的代码逻辑。catch从句后跟随finally块,后者中放置清理代码,不管try块中是否产生异常,finally块内的逻辑总是会执行。尽管catch和finally都是可选的,但try从句需要至少二者之一与之组成完整的语句

try/catch/finally语句块都需要使用花括号括起来,这里的花括号是必需的,即使从句中只有一条语句也不能省略花括号

try{
  //通常来讲,这里的代码会从头到尾而不会产生任何问题
  //但有时会抛出一个异常,要么是由throw语句直接抛出,要么通过调用一个方法间接抛出
}catch(e){
  //当且仅当try语句块抛出了异常,才会执行这里的代码
  //这里可以通过局部变量e来获得对Error对象或者抛出的其他值的引用
  //这里的代码块可以基于某种原因处理这个异常,也可以忽略这个异常,还可以通过throw语句重新抛出异常
}finally{
  //不管try语句是否抛出了异常,finally里的逻辑总是会执行,终止try语句块的方式有:
  //1、正常终止,执行完语句块的最后一条语句
  //2、通过break、continue或return语句终止
  //3、抛出一个异常,异常被catch从句捕获
  //4、抛出一个异常,异常未被捕获,继续向上传播
}

一般地,把所有可能会抛出错误的代码都放在try语句块中,而把那些用于错误处理的代码放在catch块中

如果try块中的任何代码发生了错误,就会立即退出代码执行过程,然后接着执行catch块。此时,catch块会接收到一个错误信息的对象,这个对象中包含的实际信息会因浏览器而异,但共同的是有一个保存着错误消息的message属性

[注意]一定要给error对象起个名字,置空会报语法错误

try{
  q;
}catch(error){
  alert(error.message);//q is not defined
}

//Uncaught SyntaxError: Unexpected token )
try{
  q;
}catch(){
  alert(error.message);
}

catch接受一个参数,表示try代码块抛出的值

function throwIt(exception) {
 try {
  throw exception;
 } catch (e) {
  console.log('Caught: '+ e);
 }
}

throwIt(3);// Caught: 3
throwIt('hello');// Caught: hello
throwIt(new Error('An error happened'));// Caught: Error: An error happened

catch代码块捕获错误之后,程序不会中断,会按照正常流程继续执行下去

try{
 throw "出错了";
} catch (e) {
 console.log(111);
}
console.log(222);
// 111
// 222

为了捕捉不同类型的错误,catch代码块之中可以加入判断语句

try {
 foo.bar();
} catch (e) {
 if (e instanceof EvalError) {
  console.log(e.name + ": " + e.message);
 } else if (e instanceof RangeError) {
  console.log(e.name + ": " + e.message);
 }
 // ...
}

虽然finally子句在try-catch语句中是可选的,但finally子句一经使用,其代码无论如何都会执行。换句话说,try语句块中的代码全部正常执行,finally子句会执行;如果因为出错而执行了catch语句块,finally子句照样还会执行。只要代码中包含finally子句,则无论try或catch语句块中包含什么代码——甚至return语句,都不会阻止finally子句的执行

//由于没有catch语句块,所以错误没有捕获。执行finally代码块以后,程序就中断在错误抛出的地方
function cleansUp() {
 try {
  throw new Error('出错了……');
  console.log('此行不会执行');
 } finally {
  console.log('完成清理工作');
 }
}
cleansUp();
// 完成清理工作
// Error: 出错了……
function testFinnally(){
  try{
    return 2;
  }catch(error){
    return 1;
  }finally{
    return 0;
  }
}
testFinnally();//0

[注意]return语句的count的值,是在finally代码块运行之前,就获取完成了

var count = 0;
function countUp() {
 try {
  return count;
 } finally {
  count++;
 }
}
countUp();// 0
console.log(count);// 1
function f() {
 try {
  console.log(0);
  throw "bug";
 } catch(e) {
  console.log(1);
  return true; // 这句原本会延迟到finally代码块结束再执行
  console.log(2); // 不会运行
 } finally {
  console.log(3);
  return false; // 这句会覆盖掉前面那句return
  console.log(4); // 不会运行
 }
 console.log(5); // 不会运行
}
var result = f();
// 0
// 1
// 3

console.log(result);// false

【tips】块级作用域

try-catch语句的一个常见用途是创建块级作用域,其中声明的变量仅仅在catch内部有效

ES6引入了let关键字,为其声明的变量创建块级作用域。但是,在目前ES3和ES5的情况下,常常使用try-catch语句来实现类似的效果

由下面代码可知,e仅存在于catch分句内部,当试图从别处引用它时会抛出错误

try{
  throw new Error();//抛出错误
}catch(e){
  console.log(e);//Error(…)
}
console.log(e);//Uncaught ReferenceError: e is not defined

常见错误错误处理的核心是首先要知道代码里会发生什么错误。由于javaScript是松散类型的,而且也不会验证函数的参数,因此错误只会在代码期间出现。一般来说,需要关注三种错误:类型转换错误、数据类型错误、通信错误

【类型转换错误】

类型转换错误发生在使用某个操作符,或者使用其他可能自动转换值的数据类型的语言结构时

容易发生类型转换错误的地方是流控制语句。像if之类的语句在确定下一步操作之前,会自动把任何值转换成布尔值。尤其是if语句,如果使用不当,最容易出错

未使用过的命名变量会自动被赋予undefined值。而undefined值可以被转换成布尔值false,因此下面这个函数中的if语句实际上只适用于提供了第三个参数的情况。问题在于,并不是只有undefined才会被转换成false,也不是只有字符串值才可以转换为true。例如,假设第三个参数是数值0,那么if语句的测试就会失败,而对数值1的测试则会通过

function concat(str1,str2,str3){
  var result = str1 + str2;
  if(str3){ //绝对不要这样
    result += str3;
  }
  return result;
}

在流控制语句中使用非布尔值,是极为常见的一个错误来源。为避免此类错误,就要做到在条件比较时切实传入布尔值。实际上,执行某种形式的比较就可以达到这个目的

function concat(str1,str2,str3){
  var result = str1 + str2;
  if(typeof str3 == 'string'){ //更合适
    result += str3;
  }
  return result;
}

【数据类型错误】

javascript是松散类型的,在使用变量和函数参数之前,不会对它们进行比较以确保它们的数据类型正确。为了保证不会发生数据类型错误,只能编写适当的数据类型检测代码。在将预料之外的值传递绘函数的情况下,最容易发生数据类型错误

//不安全的函数,任何非数组值都会导致错误
function reverseSort(values){
  if(values){
    values.sort();
    values.reverse();
  }
}

另一个常见的错误就是将参数与null值进行比较。与null进行比较只能确保相应的值不是null和undefined。要确保传入的值有效,仅检测null值是不够的

//不安全的函数,任何非数组值都会导致错误
function reverseSort(values){
  if(values != null){
    values.sort();
    values.reverse();
  }
}

如果传入一个包含sort()方法的对象(而不是数组)会通过检测,但调用reverse()函数时可能会出错

//不安全的函数,任何非数组值都会导致错误
function reverseSort(values){
  if(typeof values.sort == 'function'){
    values.sort();
    values.reverse();
  }
}

在确切知道应该传入什么类型的情况下,最好是使用instanceof来检测其数据类型

//安全,非数组值被忽略
function reverseSort(values){
  if(values instanceof Array){
    values.sort();
    values.reverse();
  }
}

【通信错误】

随着Ajax编程的兴起,Web应用程序在其生命周期内动态加载信息或功能,已经成为一件司空见惯的事。不过,javascript与服务器之间的任何一次通信,都有可能会产生错误

最常见的问题是在将数据发送给服务器之前,没有使用encodeURIComponent()对数据进行编码

//错误
http://www.yourdomain.com/?redir=http://www.sometherdomain.com?a=b&c=d
//针对'redir='后面的所有字符串调用encodeURIComponent()就可以解决这个问题
http://www.yourdomain.com/?redir=http:%3A%2F%2Fwww.sometherdomain.com%3Fa%3Db%26c%3Dd

以上这篇全面了解javascript中的错误处理机制就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
教你在heroku云平台上部署Node.js应用
Jul 30 Javascript
node.js中的dns.getServers方法使用说明
Dec 08 Javascript
js实现文字跟随鼠标移动而移动的方法
Feb 28 Javascript
JS实现控制表格行文本对齐的方法
Mar 30 Javascript
jquery分析文本里url或邮件地址为真实链接的方法
Jun 20 Javascript
DOM操作原生js 的bug,使用jQuery 可以消除的解决方法
Sep 04 Javascript
js日期相关函数dateAdd,dateDiff,dateFormat等介绍
Sep 24 Javascript
浅谈jquery选择器 :first与:first-child的区别
Nov 20 Javascript
关于 jQuery Easyui异步加载tree的问题解析
Dec 06 Javascript
基于JavaScript实现表格滚动分页
Nov 22 Javascript
vue指令之表单控件绑定v-model v-model与v-bind结合使用
Apr 17 Javascript
vue请求本地自己编写的json文件的方法
Apr 25 Javascript
JavaScript中ES6 Babel正确安装过程
Jul 18 #Javascript
深入理解JS正则表达式---分组
Jul 18 #Javascript
基于jQuery实现弹出可关闭遮罩提示框实例代码
Jul 18 #Javascript
简单的JS轮播图代码
Jul 18 #Javascript
JavaScript获取css行间样式,内连样式和外链样式的简单方法
Jul 18 #Javascript
Bootstrap零基础学习第一课之模板
Jul 18 #Javascript
深入分析javascript中的错误处理机制
Jul 17 #Javascript
You might like
php 将excel导入mysql
2009/11/09 PHP
详解WordPress中给链接添加查询字符串的方法
2015/12/18 PHP
php面向对象值单例模式
2016/05/03 PHP
jquery ajaxSubmit 异步提交的简单实现
2014/02/28 Javascript
js判断字符长度及中英文数字等
2014/03/19 Javascript
借助JavaScript脚本判断浏览器Flash Player信息的方法
2014/07/09 Javascript
基于jQuery Tipso插件实现消息提示框特效
2016/03/16 Javascript
极力推荐10个短小实用的JavaScript代码段
2016/08/03 Javascript
浅析Node.js实现HTTP文件下载
2016/08/05 Javascript
bootstrap——bootstrapTable实现隐藏列的示例
2017/01/14 Javascript
用vue和node写的简易购物车实现
2017/04/25 Javascript
JS+H5 Canvas实现时钟效果
2018/07/20 Javascript
微信小程序textarea层级过高的解决方法
2019/03/04 Javascript
python根据开头和结尾字符串获取中间字符串的方法
2015/03/26 Python
Python爬虫爬取美剧网站的实现代码
2016/09/03 Python
Python编程之string相关操作实例详解
2017/07/22 Python
Python实现爬取亚马逊数据并打印出Excel文件操作示例
2019/05/16 Python
python和mysql交互操作实例详解【基于pymysql库】
2019/06/04 Python
Python变量作用域LEGB用法解析
2020/02/04 Python
tensorflow常用函数API介绍
2020/04/19 Python
Tensorflow中批量读取数据的案列分析及TFRecord文件的打包与读取
2020/06/30 Python
Python接口自动化测试框架运行原理及流程
2020/11/30 Python
怎样实现H5+CSS3手指滑动切换图片的示例代码
2019/05/05 HTML / CSS
澳大利亚的奢侈品牌:Oroton
2016/08/26 全球购物
凯普林包包西班牙官网:Kipling西班牙
2019/04/12 全球购物
体育纪念品、亲笔签名的体育收藏品:Steiner Sports
2020/07/31 全球购物
Boolean b = new Boolean(“abcde”); 会编译错误码
2013/11/27 面试题
专科应届生求职信
2013/11/24 职场文书
犯错检讨书
2014/02/21 职场文书
学习决心书
2014/03/11 职场文书
安全协议书
2014/04/23 职场文书
计算机多媒体专业自荐信
2014/07/04 职场文书
房产协议书范本2014
2014/09/30 职场文书
公司地址变更通知
2015/04/25 职场文书
对学校的意见和建议
2015/06/04 职场文书
MATLAB 全景图切割及盒图显示的实现步骤
2021/05/14 Python