ECMAScript 2017 (ES8) 中的尾调用优化

阅读时长 4 分钟读完

尾调用优化 (Tail Call Optimization, TCO) 是一项 JavaScript 的优化技术,用于优化尾递归函数的性能。在尾递归函数中,递归调用是函数的最后一步操作,如果优化成功,可以使得函数的调用栈不会无限增加,从而避免了栈溢出的风险。

什么是尾调用?

尾调用是指函数在执行完自己的操作后,返回一个函数调用的结果,且该函数调用是该函数执行流的最后一步操作。下面是一个简单的例子:

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

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

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

addFive 函数中,最后一步操作是调用 add 函数,且该函数的返回值就是 addFive 的返回值。因此,这是一个尾调用。

尾递归函数

尾递归函数是一类特殊的尾调用函数,它们调用自身,并且递归调用是函数的最后一步操作。在某些情况下,这些函数的性能可能会受到调用栈的限制,从而导致栈溢出的风险。而尾调用优化技术就是用于解决这个问题的。

下面是一个递归计算斐波那契数列的函数:

在该函数中,递归调用 fibonacci 函数是最后执行的操作,因此它不是尾递归函数。那么,如何将其改为尾递归函数呢?

尾递归优化的思想是让递归调用成为函数的最后一步操作,并且将函数的每一次递归调用的结果作为参数传递给下一次调用,从而避免了调用栈的无限增长。

接下来是一个尾递归实现的斐波那契数列计算函数:

在该函数中,递归调用 fibonacci 函数是最后执行的操作,并且每次调用的结果都被传递给下一次调用。因此,它是一个尾递归函数。

尾调用优化的实现

在 ES6 之前,JavaScript 中并没有指定尾调用优化的规范,因此不同的浏览器厂商、JavaScript 引擎的实现方式可能略有不同。

在 ECMAScript 2015 (ES6) 中,该规范明确了尾调用优化的规则,并且要求符合规则的函数必须进行优化。具体说来,一个函数符合尾调用优化的规则需要满足以下条件:

  1. 函数的最后一个操作是通过返回一个函数调用的结果来完成的。
  2. 该函数调用不能在一个闭合的函数中完成,也就是说,不能有当前函数的词法作用域嵌套在调用函数的词法作用域中。

可以看出,尾调用优化的规则并不是很宽松,只有满足特定条件的函数才能够进行优化。因此,在实际编程中,需要特别注意函数的编写方式。

尾调用优化的实现原理是对当前函数的执行栈进行复用。如果满足优化条件,JavaScript 引擎会将当前函数的执行栈销毁,并且重新用于调用下一个函数,从而实现了对递归调用的优化。这样,就避免了函数调用栈无限增长的情况,提高了程序的性能。

总结

尾调用优化是一项提高 JavaScript 程序性能的重要技术。在 ES6 中,该技术得到了规范化,使得函数能够更好地处理递归调用的情况,并避免因调用栈溢出而导致程序崩溃的风险。在实际编程中,针对特定的函数,我们可以通过特定的编码技巧,来实现尾调用优化。尾调用优化对于编写高效的 JavaScript 程序非常重要。

示例代码

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

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

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

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

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

纠错
反馈