深入分析javascript中的错误处理机制


Posted in Javascript onJuly 17, 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 相关文章推荐
fromCharCode和charCodeAt 方法
Dec 27 Javascript
JQuery 文本框使用小结
May 22 Javascript
使用jQuery内容过滤选择器选择元素实例讲解
Apr 18 Javascript
JS实现浏览器状态栏文字从右向左弹出效果代码
Oct 27 Javascript
jQuery+css实现的换页标签栏效果
Jan 27 Javascript
javascript this详细介绍
Sep 19 Javascript
jquery学习笔记之无new构建详解
Dec 07 jQuery
JavaScript获取移动设备型号的实现代码(JS获取手机型号和系统)
Mar 10 Javascript
vue.js实现点击后动态添加class及删除同级class的实现代码
Apr 04 Javascript
jQuery AJAX 方法success()后台传来的4种数据详解
Aug 08 jQuery
Javascript操作select控件代码实例
Feb 14 Javascript
vue2.x数组劫持原理的实现
Apr 19 Javascript
javascript正则表达式中分组详解
Jul 17 #Javascript
最佳的JavaScript错误处理实践
Jul 16 #Javascript
JS+CSS3实现超炫的散列画廊特效
Jul 16 #Javascript
js canvas实现擦除动画
Jul 16 #Javascript
微信jssdk用法汇总
Jul 16 #Javascript
详解JavaScript节流函数中的Throttle
Jul 16 #Javascript
很棒的js选项卡切换效果
Jul 15 #Javascript
You might like
php email邮箱正则
2008/10/08 PHP
ExtJS与PHP、MySQL实现存储的方法
2010/04/02 PHP
PHP中error_log()函数的使用方法
2015/01/20 PHP
php实现在服务器端调整图片大小的方法
2015/06/16 PHP
Joomla数据库操作之JFactory::getDBO用法
2016/05/05 PHP
PHP全功能无变形图片裁剪操作类与用法示例
2017/01/10 PHP
完美解决在ThinkPHP控制器中命名空间的问题
2017/05/05 PHP
jquery实现表格奇数偶数行不同样式(有图为证及实现代码)
2013/01/23 Javascript
jQuery的缓存机制浅析
2014/06/07 Javascript
js实现字符串转日期格式的方法
2015/05/20 Javascript
JS动态改变浏览器标题的方法
2016/04/06 Javascript
jQuery操作iframe中js函数的方法小结
2016/07/06 Javascript
jquery实现拖动效果
2016/08/10 Javascript
js实现抽奖效果
2017/03/27 Javascript
关于js中的鼠标事件总结
2017/07/11 Javascript
深入浅析javascript继承体系
2017/10/23 Javascript
node前端模板引擎Jade之标签的基本写法
2018/05/11 Javascript
vue vue-Router默认hash模式修改为history需要做的修改详解
2018/09/13 Javascript
复制粘贴功能的Python程序
2008/04/04 Python
Python的Django框架中模板碎片缓存简介
2015/07/24 Python
python简单实现获取当前时间
2016/08/27 Python
基于Python socket的端口扫描程序实例代码
2018/02/09 Python
python微元法计算函数曲线长度的方法
2018/11/08 Python
python 字符串只保留汉字的方法
2018/11/16 Python
对python捕获ctrl+c手工中断程序的两种方法详解
2018/12/26 Python
Django实现内容缓存实例方法
2020/06/30 Python
类的返射机制中的包及核心类
2016/09/12 面试题
Java如何读取CLOB字段
2013/10/10 面试题
大学生求职推荐信
2013/11/27 职场文书
小学母亲节活动方案
2014/03/14 职场文书
共产党员公开承诺书范文
2014/03/28 职场文书
巾帼建功标兵事迹材料
2014/05/11 职场文书
开学典礼演讲稿
2014/05/23 职场文书
老干部工作汇报材料
2014/10/28 职场文书
在python中实现导入一个需要传参的模块
2021/05/12 Python
使用pandas或numpy处理数据中的空值(np.isnan()/pd.isnull())
2021/05/14 Python