ES12 中的尾递归优化详解

阅读时长 5 分钟读完

随着前端代码越来越复杂,性能也在被越来越多地考虑。ES6 中引入的尾递归优化对于一些需要递归的算法提供了一种优化方式,而 ES12 又对尾递归进行了一定的优化,本文将对此进行详细解析。

什么是尾递归?

在了解尾递归优化之前,需要先理解尾递归。简单来说,尾递归就是一个函数在最后一步调用自己。比如以下的斐波那契数列:

在执行 fibonacci(5) 的时候,会产生如下的递归调用栈:

-- -------------------- ---- -------
------------
  ------------
    ------------
      ------------
        ------------
        ------------
      ------------
    ------------
      ------------
      ------------
  ------------
    ------------
      ------------
      ------------
    ------------

可以看到,每次调用 fibonacci() 的时候,都需要保存上一个调用的状态,等到最后一次调用完成后,再一层层返回上去。这种调用方式会产生很多的函数调用栈,容易造成堆栈溢出等问题,导致程序运行崩溃。

而尾递归就可以避免这种问题。尾递归调用是指在函数的最后一步调用自身,这个调用操作的返回值不被任何操作所引用或使用,也就是说,调用的结果直接作为当前调用的结果返回,不会再产生新的调用栈。针对上面的斐波那契数列,可以将其改写为尾递归:

这种尾递归调用方法,不仅不会复制多个调用栈,还可以优化执行速度。

ES6 中引入的尾递归优化

ES6 中的尾递归优化,针对上述的递归操作,保证了在不增加额外执行期间和内存使用的同时,避免了堆栈溢出问题。此优化要求递归操作是尾递归。简单来说,在代码执行完成后不会造成额外的操作负担。

实现这一优化需要使用 JavaScript 引擎的尾调用优化功能,这个优化可以消除一些函数调用的开销。具体来说,这意味着 JavaScript 引擎将不使用额外的内存来存放递归调用栈,而是在递归调用中重用同一个堆栈帧。

而在 ES6 中,只有在严格模式下,JavaScript 引擎才会使用这个优化。严格模式的设置,可以在代码第一行加上 'use strict';

ES12 中的尾递归优化

虽然 ES6 中引入了尾递归优化,避免了堆栈溢出问题,但是执行效率并没有完全得到提升。因为在执行递归操作的时候,还是需要一个不断增长的函数调用栈来进行保存和操作,所以时间复杂度并没有得到优化。

而 ES12 中对尾递归进行了优化。如果你使用了 tailCallOptimization 标志启用了 ES12,那么函数的尾调用优化支持 TCO。新的实现允许在字符串、数学操作、转换和逻辑操作等操作链中进行尾调用优化。

具体的使用方法是,在代码的开头加上 @annotation.tailCalls,就可以启用 TCO。比如:

-- -------------------- ---- -------
---- --------
------ - ---------- - ---- ------------------

---------------------
-------- ------ ---- - -- ---- - -- -
  -- -- --- -- -
    ------ -----
  -
  ------ ----- - -- ----- ---- - ------
-

-------------------------

这个例子中,我们通过 annotation.tailCalls 指令启用了 ES12 中的尾递归优化。这样,在执行 fib(100000) 就不会爆栈了。

总结

尾递归是一种比较常见的算法优化方式,虽然在 ES6 中已经引入了尾递归优化,但是代码执行速度仍需要进一步优化。ES12 中,通过启用 TCO 可以更加有效地利用尾递归来提高代码执行效率。在实际开发中,可以根据具体场景来选择是否使用尾递归优化,以达到更好的代码执行效果。

参考链接

来源:JavaScript中文网 ,转载请注明来源 https://www.javascriptcn.com/post/6493dca948841e9894172520

纠错
反馈