6.6 异常处理
异常处理语句包括throw、try、catch、finally,下面详细介绍。
6.6.1 异常概述
异常(Exception)是一个信号,提示代码发生了超出常规预设的行为或结果,异常也可能是程序发生错误的一种征兆。JavaScript有一套完善的异常处理机制,保证程序不因为异常而崩溃。
异常处理机制就是一套应对JavaScript代码发生错误时的处理方法,这套方法被封装在try/catch/finally结构中,把代码放在这个结构中执行就可以避免异常发生。
JavaScript把所有可能的错误分门别类地进行整理,并把它们封装在不同的对象中,这就是异常的种类。JavaScript内置的异常对象包括Error、EvalError、RangeError、SyntaxError、TypeError、ReferenceError和URIError。具体说明如表6-5所示。
表6-5 JavaScript内置异常对象
除了内置异常对象外,JavaScript允许用户使用自定义异常对象。
6.6.2 throw语句
throw语句能够主动抛出一个异常,告诉系统发生了异常状况或错误。throw语句的语法格式如下:
throw expression;
expression可以是任意类型的表达式,一般常用它来声明Error对象或者Error子类的一个实例。
【示例】在下面循环结构中,定义了一个异常并使用throw语句把它抛出来,这样当循环变量大于5时,系统会自动弹出一个编译错误,提示“循环变量的值大于5了”的错误信息。
for(var i = 0; i<10; i++){ if(i>5) throw new Error("循环变量的值大于5了"); //定义并抛出一个异常 }
在抛出异常时,JavaScript解释器会停止程序的正常执行,并跳转到与其最近的异常处理器(catch结构)。如果解释器没有找到异常处理器,则会检查上一级的catch结构,并依此类推,直到找到一个异常处理器为止。如果在程序中没有找到任何异常处理器,将会视其为错误并显示出来。
6.6.3 try/catch/finally语句
不管是系统抛出,还是用户有意抛出异常,都需要捕获(catch)异常,以便采取适当的动作把程序从异常状态恢复到正常运行状态。
try/catch/finally语句是JavaScript异常处理器,其中try从句负责指明需要处理的代码块。catch从句负责捕获异常,并决定应对之策。finally从句负责后期处理工作,如清除代码、释放资源等。不管异常是否发生,finally从句最后都是要执行的。整个异常处理的结构和从句之间的相互关系如下:
try { //调试代码块 } catch(e) { //捕获异常并进行处理 } finally { //后期事务处理 }
正常情况下,程序按顺序执行try从句中的代码,如果没有异常发生,将会忽略catch从句,跳转到finally从句中继续执行。如果在try从句中发生运行时错误或者使用throw语句主动抛出异常,则执行catch从句代码块,在该从句中通过参数变量引用抛出的Error对象或者其他值,同时定义处理异常的方法,或者忽略不计,或者再次抛出异常等。
注意:在异常处理结构中,大括号不是复合语句的一部分,而是异常处理结构的一部分,任何时候都不能够省略这些大括号。
【示例1】在下面的代码中,先在try从句中制造一个语法错误,即字符串没有加引号,然后在catch从句中利用参数变量b获取Error对象的引用,然后提示错误的详细信息,最后在finally从句中弹出正确的信息。
try{ //测试代码 alert(a); //制作语法错误 } catch(b){ //捕获错误 alert(b.message); //提示错误信息 } finally{ //异常后期处理 alert("a"); //提示正确值 }
【示例2】在异常处理结构中,catch和finally从句是可选项目,可以根据需要省略,但在正常情况下必须包含try和catch从句。上面示例可以精简为:
try{ alert(a); } catch(b){}
finally从句比较特殊,不管try语句是否完全执行,finally语句最后都必须要执行,即使使用了跳转语句跳出了异常处理结构,也必须在跳出之前先执行finally从句。
如果没有catch从句,JavaScript在执行完try从句之后,继续执行finally从句,如果发生异常,会继续沿着语法结构链向上查找上一级catch从句。
try/catch语句可以相互嵌套,甚至可以在内层try/catch语句中又嵌套另一个内层try/catch语句,以及在该内层try/catch语句中再嵌套一个try/catch语句,嵌套的层数取决于实际代码的意义。
为什么需要使用嵌套的try/catch语句呢?因为使用嵌套的try/catch语句,可以逐步处理内层的try/catch语句抛出的异常。
【示例3】下面代码就是一个多层嵌套的异常结构,在处理一系列的异常时,内层的catch子句通过将异常抛出,就可以将异常抛给外层的catch子句来处理。
try{ //外层异常处理结构 try{ //内层异常处理结构 alret("Hi"); //错误引用方法 } catch(exception){ var e; if(! exception.description){ //兼容非IE浏览器 e=exception.name; //获取错误名称 } else{ //兼容IE浏览器 e=exception.description; //获取错误描述信息 } if (e == "Object expected" || e == "ReferenceError"){ //如果是此类错误信息,则提示这样信息 alert("内层try/catch能够处理这个错误"); } else{ //否则再一次抛出一个异常 throw exception; } } } catch(exception){ //获取内层异常处理结构中抛出的异常 alert("内层try/catch不能够处理这个错误"); }
6.6.4 案例:改变作用域链
一般来说,一个运行期上下文的作用域链不会被改变,但是在JavaScript中,使用with或try/catch语句可以人为改变运行期上下文的作用域链。
当try子句块发生错误时,程序会自动转入catch子句块,并将异常对象推入到作用域链前端的一个可变的错误对象中。在catch子句块中,函数的所有局部变量现在被放在第二个作用域链对象中,例如:
try { methodThatMightCauseAnError(); } catch (ex) { alert(ex.message); }
注意:只要catch子句执行完毕,作用域链就会返回到原来的状态。
如果使用得当,try/catch结构将是非常有用的语句,可以通过精缩代码的办法最小化catch子句对性能的影响。一个很好的模式是将错误交给一个专用函数来处理,例如:
try { methodThatMightCauseAnError(); } catch (ex) { handleError(ex); }
handleError()函数是catch子句中运行的唯一代码。此函数以适当方法自由地处理错误,并接收由错误产生的异常对象。由于只有一条语句,没有局部变量访问,作用域链临时改变就不会影响代码的性能。