深入分析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 相关文章推荐
javascript 数组排序函数
Aug 20 Javascript
node.js中的console.error方法使用说明
Dec 10 Javascript
JS实现获取键盘按下的按键并显示在页面上的方法
Nov 04 Javascript
深入理解requestAnimationFrame的动画循环
Sep 20 Javascript
H5移动端图片压缩上传开发流程
Nov 09 Javascript
jquery结合html实现中英文页面切换
Nov 29 Javascript
详解js中常规日期格式处理、月历渲染和倒计时函数
Dec 28 Javascript
浅谈JS封闭函数、闭包、内置对象
Jul 18 Javascript
JS实现的简单拖拽购物车功能示例【附源码下载】
Jan 03 Javascript
深入浅析Vue.js 中的 v-for 列表渲染指令
Nov 19 Javascript
微信小程序自定义联系人弹窗
May 26 Javascript
在Vue中使用CSS3实现内容无缝滚动的示例代码
Nov 27 Vue.js
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
Yii2框架实现注册和登录教程
2016/09/30 PHP
Laravel 5.5官方推荐的Nginx配置学习教程
2017/10/06 PHP
laravel 模型查询按照whereIn排序的示例
2019/10/16 PHP
php+iframe 实现上传文件功能示例
2020/03/04 PHP
jquery获取复选框被选中的值
2014/04/10 Javascript
JavaScript 里的类数组对象
2015/04/08 Javascript
微信小程序 HTTPS报错整理常见问题及解决方案
2016/12/14 Javascript
js如何判断是否在iframe中及防止网页被别站用iframe嵌套
2017/01/11 Javascript
AngularJS中的缓存使用
2017/01/11 Javascript
详解Vue2.0之去掉组件click事件的native修饰
2017/04/20 Javascript
ActiveX控件的使用-js实现打印超市小票功能代码详解
2017/11/22 Javascript
Angular实现的自定义模糊查询、排序及三角箭头标注功能示例
2017/12/28 Javascript
React中的render何时执行过程
2018/04/13 Javascript
使用rollup打包JS的方法步骤
2018/12/05 Javascript
防止Layui form表单重复提交的实现方法
2019/09/10 Javascript
JavaScript代码模拟鼠标自动点击事件示例
2020/08/07 Javascript
[02:30]联想杯DOTA2完美世界全国高校联赛—北京站现场
2015/11/16 DOTA
Fabric 应用案例
2016/08/28 Python
Python结巴中文分词工具使用过程中遇到的问题及解决方法
2017/04/15 Python
python: 判断tuple、list、dict是否为空的方法
2018/10/22 Python
对pandas处理json数据的方法详解
2019/02/08 Python
Python的UTC时间转换讲解
2019/02/26 Python
python绘制玫瑰的实现代码
2020/03/02 Python
HTML高亮关键字的实现代码
2018/10/22 HTML / CSS
使用layui框架实现点击左侧导航切换右侧内容且右侧选项卡跟随变化的效果
2020/11/10 HTML / CSS
美国羊皮公司:Overland
2018/01/15 全球购物
物业公司采购员岗位职责
2013/12/31 职场文书
师范毕业生自我鉴定
2014/01/15 职场文书
2014年迎新年联欢会活动策划方案
2014/02/26 职场文书
中学生学雷锋活动心得体会
2014/03/10 职场文书
师德师风自我评价范文
2014/09/11 职场文书
运动会加油稿100字
2014/09/19 职场文书
2014年第四季度入党积极分子思想汇报(十八届四中全会)
2014/11/03 职场文书
男方家长婚礼答谢词
2015/09/29 职场文书
《月球之谜》教学反思
2016/02/20 职场文书
Win10加载疑难解答时出错发生意外错误的解决方法
2022/07/07 数码科技