JavaScript 异常处理和预编译
JavaScript 异常处理
try+catch+finally+throw
try 语句测试代码块的错误。
catch 语句处理错误。
throw 语句创建自定义错误。
finally 语句在 try 和 catch 语句之后,无论是否有触发异常,该语句都会执行。
语法
try/catch/finally语句
try/catch/finally 是 JavaScript 异常处理语句。语法格式如下:
try{
//调试代码块
}
catch(e) {
//捕获异常,并进行异常处理的代码块
}
finally{
//后期清理代码块
}
ps: 在正常情况下,JavaScript 按顺序执行 try 子句中的代码,如果没有异常发生,将会忽略 catch 子句,跳转到 finally 子句中继续执行。
如果在 try 子句运行时发生错误,或者使用 throw 语句主动抛出异常,则执行 catch 子句中的代码,同时传入一个参数,引用 Error 对象。
例:
在下面的例子中,在 try 块的代码中写了一个错字。
catch 块会捕捉到 try 块中的错误,并执行代码来处理它。
var txt="";
function message()
{
try {
adddlert("Welcome guest!");
} catch(err) {
txt="本页有一个错误。\n\n";
txt+="错误描述:" + err.message + "\n\n";
txt+="点击确定继续。\n\n";
alert(txt);
}
}
finally 语句
finally 语句不论之前的 try 和 catch 中是否产生异常都会执行该代码块。
不管 try 语句是否完全执行,finally 语句最后都必须要执行,即使使用了跳转语句跳出了异常处理结构,也必须在跳出之前先执行 finally 子句。
例:
/*在函数体内设计一个异常处理结构,为每个子句添加一个 return 语句。
调用函数后,实际返回的是“finally”,而不是“try”,
因为 finally 子句必须最后执行,把 finally 子句去掉,
函数才会返回“try”。*/
function test() {
try {
return "try";
}catch {
return "catch";
}finally {
return "finally";
}
}
console.log(test()); //返回“finally”
ps:
try/catch/finally 语句允许嵌套使用,嵌套的层数不限,同时形成一条词法作用域链。在 try 中发生异常时,JavaScript 会停止程序的正常运行,并跳转到层级最近的 catch 子句(异常处理器)。
如果没有找到异常处理器,则会沿着作用域链,检查上一级的 catch 子句,以此类推,直到找到一个异常处理器。如果在程序中没有找到任何异常处理器,将会显示错误。
例:
/*下面代码就是一个多层嵌套的异常结构,在处理一系列的异常时,内层的
catch 子句通过将异常抛出,
就可以将异常抛给外层的 catch 子句来处理。*/
try { //外层异常处理结构
try { //内层异常处理结构
test(); //错误调用
}
catch(error) {
if (error.name == "ReferenceError") console.log("错误参考"); //如果是异常引用,则提示这样的信息
else throw error; //否则再次抛出一个异常,并把错误信息向上传递
}
}
catch (error) { //获取内层异常处理结构中抛出的异常
console.log("内层 try/catch 不能够处理这个错误");
}
Throw 语句
throw 语句允许我们创建自定义错误。
创建或抛出异常(exception)。
如果把 throw 与 try 和 catch 一起使用,那么您能够控制程序流,并生成自定义的错误消息。
语法
throw exception
异常可以是 JavaScript 字符串、数字、逻辑值或对象。
例:
本例检测输入变量的值。如果值是错误的,会抛出一个异常(错误)。catch 会捕捉到这个错误,并显示一段自定义的错误消息:
function myFunction() {
var message, x;
message = document.getElementById("message");
message.innerHTML = "";
x = document.getElementById("demo").value;
try {
if(x == "") throw "值为空";
if(isNaN(x)) throw "不是数字";
x = Number(x);
if(x < 5) throw "太小";
if(x > 10) throw "太大";
}
catch(err) {
message.innerHTML = "错误: " + err;
}
}
注意 如果 getElementById 函数出错,上面的例子也会抛出一个错误。
错误类型
ECMA-262 规范了 7 种错误类型,具体说明如下。其中 Error 是基类,其他 6 种错误类型是子类,都继承 Error 基类。Error 类型的主要用途是自定义错误对象。
Error:普通异常。与 throw 语句和 try/catch 语句一起使用,属性 name 可以读写异常类型,message 属性可以读写详细错误信息。
SyntaxError:出现语法错误时抛出。
RangeError:数字超出合法范围时抛出、
ReferenceError:读取不存在的变量时抛出。
TypeError:值得类型发生错误时抛出。
EvalError:不正确的使用 eval() 方法时抛出。
URIError:URI 编码和解码错误时抛出。
JavaScript 预编译
JavaScript运行的步骤
1、语法分析
2、预编译
3、解释执行
在执行代码前,还有两个步骤
语法分析 就是引擎检查你的代码有没有什么低级的语法错误
预编译 简单理解就是在内存中开辟一些空间,存放一些变量与函数
解释执行 执行代码了
JavaScript预编译(变量提升和函数提升)
JS并不会完全按照代码顺序进行解析执行,而是在解析之前进行一次“预编译”。在此过程中,会把:
(1)定义式的函数优先执行
(2)所有var变量定义,默认值为undefined、
函数声明和变量赋值
function fun() {} / 函数声明 这种形式的写法是函数声明,也即是声明一个函数,这种写法,脚本在执行之前会做预编译处理
var fun = function() {} // 变量赋值
这种写法就属于是变量的赋值了,函数在js中也是一种数据,匿名函数作为变量赋值给定义的变量。这种形式的写法,在编译阶段也会做处理,但是只会给变量fun分配一个内存空间,初始值为undefined
具体值的初始化是在程序执行阶段。
ps:
函数声明整体提前,变量 声明提前
函数中的预编译 发生在,函数执行前的那一刻
作用域 全局window(Global Object) 局部作用 (AO Activation Object)
例:
function fun1(){
//c a 暗示成了全局变量
a = 123;
var b = c = 10;
}
局部的预编译
例:
function demo(a){
console.log(d);
console.log(a);//1
var c = 123;
console.log(c);//123
function d(){}
var f = function(){}
}
demo(1);
在代码执行前
1、创建AO(activation object/执行期上下文)对象
AO = {}
2、找形参和变量声明,将变量和形参作为AO的属性名,值为undefined
AO = {
a : undefined,
c : undefined,
f : undefined
}
3、将实参值和形参统一
AO = {
a : 1,
c : undefined,
f : undefined
}
4、在函数中找函数声明,值赋予函数体
AO = {
a : 1,
c : undefined,
f : undefined,
d : function d(){}
}
执行时
AO = {
a : 1,
c : 123,
f : function(){},
d : function d(){}
}
1 test AO {
}
2 test AO{
a : undefined,
b : undefined,
}
3 test AO{
a : 10,
b : undefined,
}
4 test AO{
a : function a(){},
b : undefined,
d : function d(){}
}
全局的预编译
生成了一个GO对象 其他的三步和函数中是一致
全局的预编译,发生在全局执行前 , 打开浏览器window对象就生成了
console.log(a);//function...
a();
var a = 11;
function a (){
c = 10;//如果执行了,归属于GO
}
console.log(window.a);
console.log(a);
console.log(test);/* 输出
ƒ test(test){
console.log(test);
var test = 234;
console.log(test);
function test(){}
console.log(num++);//10
}*/
function test(test){
console.log(test);//ƒ test(){}
var test = 234;
console.log(test);//234
function test(){}
console.log(num++);//10
}
var num = 10;
test(1);
var test = 123;
console.log(test);//123
console.log(num);//11
GO{
test : undefined --> function --> 123,
num : undefined -- > 10 --> 11 //在test方法里console.log(num++);//10修改了num的值
}
testAO{
test : undefined --> 1 --> 234
}
注意
预编译阶段发生变量声明和函数声明,没有初始化行为(赋值),匿名函数不参与预编译
只有在解释执行阶段才会进行变量初始化
总结
预编译(函数执行前)
- 创建AO对象(Active Object)
- 查找函数形参及函数内变量声明,形参名及变量名作为AO对象的属性,值为undefined
- 实参形参相统一,实参值赋给形参
- 查找函数声明,函数名作为AO对象的属性,值为函数引用
预编译(脚本代码块script执行前)
- 查找全局变量声明(包括隐式全局变量声明,省略var声明),变量名作全局对象的属性,值为undefined
- 查找函数声明,函数名作为全局对象的属性,值为函数引用