8、JS 面试常考:try 里 return,finally 还会执行吗?

105 阅读2分钟

如果你写过这样的代码:

function foo() {
  try {
    return 0;
  } finally {
    console.log("a");
  }
}
console.log(foo());

你可能会惊讶:finally 居然执行了,而且函数依然返回 0!

更离谱的是,如果在 finally 里也写一个 return,会发生什么?

function foo() {
  try {
    return 0;
  } finally {
    return 1;
  }
}
console.log(foo()); // 1

答案是:finally 的 return 会覆盖 try 的 return!

这就是 JavaScript 语句执行的“黑科技”。


1. 为什么会这样?Completion Record!

JavaScript 引擎在执行语句时,会生成一个 Completion Record

  • [[type]] → 表示语句的类型(normal、break、continue、return、throw);
  • [[value]] → 表示语句的返回值(可能是表达式的值);
  • [[target]] → 语句目标(主要用于 label 语句)。

当 try 执行到 return 时,会生成一个类型为 return 的 Completion Record。
但是 finally 必须保证执行,所以引擎会在 return 之前先执行 finally。

如果 finally 自己也返回一个 Completion Record,那么它会覆盖之前的结果。


2. 普通语句 & 控制语句的执行逻辑

  • 普通语句:顺序执行,结果是 normal,继续往下走。
  • 控制语句:if、switch、try/catch,会根据内部产生的 Completion Record 决定是否中断。
  • 跳转类语句:break、continue、return、throw,会“穿透”语句块,直到被合适的结构“消费”。

比如:

{
  var i = 1;       // normal
  return i;        // return
  i++;             // 不会执行
}

这个 block 的 Completion Record 类型就是 return,它会直接中断后续执行。


3. finally 的铁律

  • finally 一定会执行;
  • finally 可以覆盖 try/catch 的 return 或 throw;
  • finally 内部的异常,也会覆盖 try/catch 的结果。

这意味着 finally 在 JS 中几乎是“最高优先级”的逻辑。


4. 标签语句:JS 里隐藏的 goto

JS 里其实有个不常用的特性:标签语句

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

这里的 break outer 会跳出外层循环。
这种机制也是依赖 Completion Record 的 [[target]] 来实现。


总结

  • try 里的 return 并不会立即返回,finally 总是会执行。
  • finally 的 return/throw 会覆盖 try 的结果。
  • JS 用 Completion Record 来统一管理语句执行的结果。
  • 标签语句 + break/continue 是 JS 里少见的“控制流黑魔法”。

互动问题
👉 你在实际项目里,有没有因为 finally 的覆盖机制踩过坑?
欢迎留言分享,大家一起少踩坑!