try catch详解

114 阅读3分钟

为什么try里面放return, finally还会执行

代码示例

function foo() {
  debugger;
  try {
    return 0;
  } catch(err) {
    console.log(err);
  } finally {
    console.log("a");
  }
}
console.log(foo());
// 执行结果:
// a
// 0

function foo() {
  try {
    return 0;
  } catch(err) {
    console.log(err);
  } finally {
    console.log("a");
    return 1;
  }
}
console.log(foo());
// 执行结果:
// a
// 1

之所以会出现上述代码中的情况,是因为在JavaScript中,每条语句的执行完成状态都是由Complettion Record(完成记录)类型表示的,它有三个字段:

  1. type: 表示完成的类型,具体有这些类型:
  • brek
  • continue
  • return
  • throw
  • normal
  1. value: 表示语句的返回值,如果没有,则是empty;

  2. target: 表示语句的目标,通常是一个JavaScript标签

    要想知道不同语句执行的内部机制,下面会重点介绍。

理解上面例子中的内部机制

普通语句

普通语句包括声明类语句(var声明,const声明,let声明,函数声明,类声明),表达式语句,空语句,with语句,debugger语句
都是从前到后顺序执行,没有任何控制和循环逻辑。
普通语句执行后,得到type为normal的Completion Record, JavaScript引擎遇到这样的Completion Record,会继续执行下一条语句。这些语句中,只有表达式语句会产生value。
浏览器在控制台中的打印就是这样的情况

语句块

语句块就是拿大括号括起来的一组语句,它是一种语句的复合结构,可以嵌套。比如下面的语句块:

{
    var i = 1;   // normal, undefined, empty
    i ++;        // normal, 1, empty
    console.log(i);   // normal, undefined, empty
}  // normal, undefined, empty

语句块本身并不复杂,但是如果其内部语句的Completion Record的type不是normal,则会打断语句块后续的语句执行流程。

let fn = function() {
    let i = 1;   // normal, empty, empty
    return i;   // return, 1, empty
    i ++;
    console.log(i)
} // return, 1, empty

fn()   // 1

假如我们在try语句中插入了一条return语句,就会产生一个非normal记录,那么整个语句块就会成为非normal类型。这种方式保证了非normal类型的语句可以穿透复杂的语句嵌套结构,从而产生控制效果。

控制型语句

控制型语句带有if、switch关键字,它们会对不同类型的Completion Record产生反应。

控制类语句分为两部分,一类会对其内部造成影响,比如:if、switch、while/for、try;另一类会对外部造成影响,比如:break、continue、return、throw。这两类语句的配合,会产生控制代码执行顺序和执行逻辑的效果,这也是我们编程的主要工作。
一般来说,for/while与break/continue,以及try与throw这样比较符合逻辑的组合,是我们比较熟悉的,但是,实际上,我们需要控制语句跟break、continue、return、throw四种类型与控制语句两两组合产生的效果。

__breakcontinuereturnthrow
if穿透穿透穿透穿透
switch消费穿透穿透穿透
for/while消费消费穿透穿透
function报错报错消费穿透
try特殊处理特殊处理特殊处理消费
catch特殊处理特殊处理特殊处理穿透
finally特殊处理特殊处理特殊处理穿透

通过上表中的内容,不难理解前文中的例子。因为finally中的语句必须保证执行, 所以当try/catch执行完毕,即使得到了return类型的完成记录,也要执行完finally中的语句。这样,当执行完finally中的语句,才得到最后的结果。

带标签的语句

前面说Completion Record类型有三个字段,最后一个为target,这涉及到JavaScript中的一个语法,带标签的语句。
标签的作用一方面是为了注释代码,增加代码的可读性;另一方面,与Completion Record中的target相配合,用于跳到特定语句。例如下面的例子:

outer:while(true) {
    inner: while(true) {
        break outer;
    }
}
console.log("finished");

break/continue语句如果后跟了关键字,会产生带target的完成记录。一旦完成记录带了target,那么只有拥有对应label的循环语句会消费它。