为什么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(完成记录)类型表示的,它有三个字段:
- type: 表示完成的类型,具体有这些类型:
- brek
- continue
- return
- throw
- normal
-
value: 表示语句的返回值,如果没有,则是empty;
-
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四种类型与控制语句两两组合产生的效果。
__ | break | continue | return | throw |
---|---|---|---|---|
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的循环语句会消费它。