在Javascript中,即使没有抛出异常,使用try-catch块是否会很耗费资源?

86

当没有任何异常被抛出时,使用多个try-catch块会使程序变慢吗?我的问题与this one相同,但是针对JavaScript。

假设我有20个函数,它们中都有try-catch块,还有另一个函数调用这20个函数中的每一个而它们都不会抛出异常。这些try-catch块会导致我的代码执行速度变慢或表现更差吗?


9
为什么在JavaScript中不经常使用try-catch?在JavaScript中,try-catch通常用于捕获和处理代码块中的异常。然而,它并不是普遍被使用的一种编程方式,这是因为:
  1. 运行时错误通常被认为是程序员的失误,应该被修复,而不是被捕获和处理。
  2. try-catch语句可能会影响代码的性能,特别是在循环中使用时,因为每次抛出异常都需要进行额外的操作。
  3. try-catch语句可能会掩盖潜在的问题,并且可能导致代码更难调试。
总之,在JavaScript中使用try-catch语句需要权衡利弊,只有当它可以解决实际问题时才应该使用。
- Chase
1
@Chase:但是那不是在讨论异常被捕获的情况吗? - Qantas 94 Heavy
2
@Qantas94Heavy - 在jsperf上有很多关于Try/Catch性能测试的好例子:http://jsperf.com/search?q=Try/Catch。在大多数情况下,`try`是相当可以忽略不计的,但这有点取决于你对“慢”的定义。如果你想要更好的性能,那么就有比`try/catch`更好的选择。@Roland也列举了一些不使用它们的好理由。 - Chase
2
@Chase 嗯,99%的jsperf测试都是有问题的,有时候它们会误导人并且提供不了有用的信息。当然,一个几乎什么也不做的函数加上try catch只会稍微慢一点点。 - Esailija
2
因此,根据其他答案,对于我的问题的简短回答是:当异常从未被抛出时,我必须突出显示的地方是没有必要的。如果不抛出异常,则不会产生任何开销。 - cprcrack
显示剩余3条评论
4个回答

81

您是否正在编写典型的CRUD UI代码?使用try catch,使用没有任何原因循环到10000次的循环,甚至使用Angular/Ember-您将不会注意到任何性能问题。

如果您正在进行低级库、物理模拟、游戏、服务器端等操作,则永远不会抛出的try-catch块通常并不重要,但问题在于V8引擎的优化编译器直到版本6才支持它,因此在语法上包含try catch的整个函数将无法被优化。不过,您可以轻松解决这个问题,只需创建一个像tryCatch这样的辅助函数:

function tryCatch(fun) {
    try {
        return fun();
    }
    catch(e) {
        tryCatch.errorObj.e = e;
        return tryCatch.errorObj;
    }
}
tryCatch.errorObj = {e: null};


var result = tryCatch(someFunctionThatCouldThrow);
if(result === tryCatch.errorObj) {
    //The function threw
    var e = result.e;
}
else {
    //result is the returned value
}
自V8版本6(随Node 8.3和最新的Chrome一起发布)以后,位于try-catch内部的代码的性能与普通代码相同。

8
如果对于V8来说是这样的话,那确实会有所不同,你的代码看起来是解决这个问题的好主意。您能提供一个陈述V8不优化带try-catch块的函数的信息来源链接吗?您是否知道其他JavaScript编译器或解释器是否也适用于此? - cprcrack
6
@cprcrack 我不确定,但是 try catch 主要在服务器端需要,所以优化并不是一个非常重要的优先事项。请参考这里的 V8 源代码,在函数中看到 try catch 时,优化编译器会退出 https://github.com/v8/v8/blob/master/src/hydrogen.cc#L3922-L3927 - Esailija
1
然而,并非所有的函数都能被优化,有些特性会阻止优化编译器对某个函数进行优化(即“退出”)。特别是,目前优化编译器会在带有 try {} catch {} 块的函数上退出! - ruhong
7
为了保持答案的最新性,需要注意的是,新的V8 TurboFan编译器将支持try-catch优化,虽然它距离进入Chrome和Node还有几个月。请参见例如:https://codereview.chromium.org/1996373002 - csvan

60
原问题涉及在未发生错误时使用try/catch的成本。将一段代码块包裹在try/catch中确实会产生影响,但是随着被保护的代码变得稍微复杂一些,try/catch的影响将迅速消失。
考虑这个测试:http://jsperf.com/try-catch-performance-jls/2 简单的递增操作每秒运行356800000次。 相同的递增操作包含try/catch后每秒只能运行93500000次。这就是try/catch带来的75%的额外开销。 但是一个简单的函数调用每秒可以运行112200000次。 两个简单函数调用每秒可以运行61300000次。
在这个测试中,未使用的try所需的时间略多于一个简单函数调用。除非在像FFT这样非常密集的内部循环中,否则这几乎不会带来任何速度惩罚。
你要避免的情况是实际抛出异常的情况。如上链接所示,这会使速度慢得多。
编辑:这些数字是对我机器上Chrome的统计。在Firefox中,未执行的try和根本不进行保护之间没有显着差异。如果没有抛出异常,使用try/catch几乎没有损失。

13
唯一一个真正通过信息提供答案而非意见的回答。 - BornReady
1
对我来说,try-no-catch是最快的代码片段,请参见http://fs5.directupload.net/images/170124/3luf37n3.png。 - timaschew
1
Firefox 50似乎已经具备了try/catch优化,但Chrome 55还没有。这是Chrome的屏幕截图:http://fs5.directupload.net/images/170124/uak7g2j8.png。 - timaschew
jsperf几年前已经下线了,对此无能为力。 - asthomas
1
这是测试的更新版本 - 在 Chrome(假定此时为 V8 的第8个版本),try...catch 不会降低性能,除非调用了一个错误。 - marksyzm
显示剩余2条评论

12
我试图根据具体的基准测试结果提供一个答案。为此,我编写了一个简单的基准测试,将try-catch与各种if-else条件从简单到更复杂进行比较。我知道基准测试可能会因平台而异。如果您得到不同的结果,请评论一下。请参见这里的try-catch基准测试首先,我尝试以紧凑的方式表示在此处的测试套件。查看上面的链接获取完整详细信息。有四个测试用例,稍后称之为(索引):
  • (1)使用try-catch块调用带有一些三角函数的lib.foo函数。永远不会抛出错误。
  • (2)使用if-else块通过'foo' in lib检查函数的存在,然后调用该函数。
  • (3)使用if-else块通过typeof lib['foo'] === 'function'检查函数的存在,然后调用该函数。
  • (4)使用if-else块通过Object.prototype.hasOwnProperty.call(lib, 'foo')检查函数的存在,然后调用该函数。

我在Chrome 87上运行了几次基准测试。尽管实际数字会不时变化,但结果是一致的,并且可以粗略地概括如下:

  • 在运行时间上,try-catch(1)和if-else(2)几乎相等。try-catch(2)有时比较慢1%至2%。
  • if-else(3)比try-catch(1)或if-else(2)慢75%。
  • if-else(4)比try-catch(1)或if-else(2)慢90%。

为了澄清,75%较慢意味着如果最快情况需要1.0秒,则较慢的执行需要1.75秒。

总之,在不抛出错误的情况下使用try-catch似乎与检查任何简单条件一样有效。如果条件有更复杂的内容,则try-catch明显更快。 作为个人笔记,结论符合我在大学所学的内容。虽然它是在C ++上下文中,但同样的教训似乎也适用于此处。如果我记得正确,我的讲师说try-block被设计成非常高效,几乎从效率上来看是看不出来的。但是,catch-block很慢,我是真的很慢。如果抛出错误,则使用catch-block进行处理所花费的时间要比使用if-else块花费的时间长数百倍甚至数千倍。因此,请将您的异常保持异常

1
只是出于好玩,我尝试将基准测试中的 if ('foo' in lib) { 这一行替换为 if (lib.foo) {,以防止 in 循环过程中的昂贵操作,虽然只有三个属性(在足够复杂的对象中可能会改变?),但 lib.foo 检查比 in 慢了 79%。对我来说很奇怪。无论如何,是的,基准测试总是真正找到答案的正确方法!感谢您设置一个。 - ruffin
@ruffin,'foo' in lib会检查空值和零吗?还是只针对未定义的情况?只是好奇 :) - st.

8
try-catch块被认为很耗费性能。然而,如果关键性能不是问题,使用它并不一定是个问题。
代价在我看来有:
- 可读性 - 在许多情况下不合适 - 在异步编程中无效 可读性:用大量的try-catch装饰代码会让代码变得丑陋和分散注意力。 不合适:如果你的代码没有异常崩溃的情况,那么插入这样的代码块是不明智的。只有在你期望代码出现故障时才插入它。请查看以下主题:何时使用try/catch块? 异步: try-catch块是同步的,在异步编程中不起作用。在ajax请求期间,你可以在专用回调函数中处理errorsuccess事件。不需要try-catch
希望这有所帮助,
R.

21
“据说”是传闻。请提供证据,不要鼓励程序员过早优化。 - Dave
8
这是胡说八道。有许多“例外”情况并不代表“失败”。例外是一种强大的编程结构。 - squarewav
1
我测试了假定变量是可调用的与先进行测试的性能。通过if语句验证比使用try/catch请求原谅快了数个数量级:http://jsperf.com/try-catch-performance-jls - Joseph Sheedy
4
关于异步函数的适用性,现在通常默认使用try catch结合ES7 async await。您可能希望更新您的回答以反映最近的发展。 - Florian Wendelborn
1
使用try/catch方法与异步回调的错误处理相比,另一个重要的区别是,在异步回调中意外抛出错误(例如拼写错误或难以发现的逻辑错误)可能会导致进程崩溃,而在catch块中意外抛出错误则不会。使用try/catch和await相比,通过promise链进行错误处理也有显著的优势,但这超出了本评论的范围。更多信息请参见:https://gist.github.com/mikermcneil/c1028d000cc0cc8bce995a2a82b29245 - mikermcneil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接