您好读者。
有时,要解决该问题,您必须使用
Recursion ,它有其优缺点。 我遇到了堆栈溢出问题。
最大递归深度受JavaScript引擎限制。 您可以准确地指望10,000个嵌套的调用,某些解释器允许更多,但对于大多数此类而言,100,000个调用超出了功能范围。 有一些自动优化可以帮助避免调用堆栈溢出(“尾递归优化”),但是到处都还不支持它们,并且仅在简单情况下可用。
递归函数的一个示例:
function sum(n) { return n === 0 ? 0 : n + sum(n - 1) } sum(5)
尾递归允许通过编译器优化调用,并且已经在
ES6标准中,但是对浏览器的支持
尚有 许多不足之处 。
尾递归函数的示例:
function sum(number, s = 0){ return number === 0 ? s : sum(number - 1, s + number) }
但是,如果没有浏览器支持,我们将面临同样的问题-代码日志 我们可以尝试与
Trampolining一起使用。
我们将编写一个装饰器,该装饰器将接受修改后的递归函数(下一步)并在不增加调用深度的情况下循环执行。
function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } }
现在,我们的递归函数应该返回一个将由装饰器立即执行的函数。 这样,我们将有条件地创建一个函数数组并依次执行它们。 但是由于每次我们都返回一个新的匿名函数,所以此代码的运行速度会稍慢一些。
function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) }
我们使用:
const trampolineSum = trampoline(sum) trampolineSum(100000)
这是我的第一篇文章。 我尝试不重述这些概念,并提供了到资源的链接。 这个主题不是唯一的,并且长期存在于英语网站上,不幸的是我没有找到俄语的主题。
谢谢您的关注。 祝你有美好的一天:)
更新资料表现:
#1递归
代码#1 function sum(n) { return n === 0 ? 0 : n + sum(n - 1) } console.time("run") sum(1000) console.timeEnd("run")
Safari 12.1.2的结果#1[调试]运行:0.353ms
[调试]运行:0.281ms
[Debug]运行:0.305ms
[调试]运行:0.315ms
[调试]运行:0.319ms
[调试]运行:0.231ms
[调试]运行:0.255ms
[调试]运行:0.334ms
[调试]运行:0.370ms
[调试]运行:0.274ms
[调试]运行:0.260ms
结果#1 Google Chrome 78.0.3892.0运行:0.118896484375ms
运行:0.101806640625ms
运行:0.099853515625ms
运行:0.10205078125ms
运行:0.10302734375ms
运行:0.099853515625ms
运行:0.106201171875ms
运行:0.103759765625ms
运行:0.105224609375ms
运行:0.262939453125ms
运行:0.136962890625ms
运行:0.10107421875ms
运行:0.10302734375ms
#2迭代
代码2 function sum(n) { let sum = 0 for (let i = 1; i <= n; i++) { sum += i } return sum } console.time("run") sum(1000) console.timeEnd("run")
Safari 12.1.2的结果#2[调试]运行:0.562ms
[调试]运行:0.552ms
[Debug]运行:0.502ms
[Debug]运行:0.527ms
[调试]运行:0.434ms
[调试]运行:0.462ms
[调试]运行:0.511ms
[Debug]运行:0.528ms
[Debug]运行:0.549ms
[Debug]运行:0.475ms
[Debug]运行:0.530ms
[调试]运行:0.494ms
Google Chrome 78.0.3892.0的结果#2运行:0.932861328125ms
运行:0.751953125ms
运行:0.4580078125ms
运行:0.678955078125ms
运行:0.424072265625ms
运行:0.505859375ms
运行:0.563720703125ms
运行:0.404052734375ms
运行:0.411865234375ms
运行:0.634033203125ms
运行:0.4169921875ms
运行:0.390869140625ms
运行:0.464111328125ms
#3尾递归和蹦床
代码3 function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } } function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) } const trampolineSum = trampoline(sum) console.time("run") trampolineSum(1000) console.timeEnd("run")
Safari 12.1.2的结果#3[Debug]运行:0.936ms
[调试]运行:0.792ms
[调试]运行:0.882ms
[调试]运行:0.826ms
[调试]运行:0.968ms
[调试]运行:0.818ms
[调试]运行:1.681ms
[调试]运行:0.777ms
[调试]运行:1.109ms
[调试]运行:0.832ms
[调试]运行:0.826ms
Google Chrome 78.0.3892.0的结果#3运行:0.60888671875ms
运行:0.989990234375ms
运行:0.567138671875ms
运行:0.56005859375ms
运行:1.0087890625ms
运行:0.5400390625ms
运行:0.578125ms
运行:0.541015625ms
运行:0.557861328125ms
运行:1.97607421875ms
运行:0.570068359375ms
运行:0.593017578125ms
运行:0.530029296875ms
运行:0.89794921875ms
运行:0.590087890625ms
#4尾巴递归和蹦床(数量众多)
代码#4 function trampoline(fn) { return function(...args) { let result = fn.apply(fn.context, args) while (typeof result === 'function') { result = result() } return result } } function sum(number, s = 0) { return number === 0 ? s : () => sum(number - 1, s + number) } const trampolineSum = trampoline(sum) console.time("run") trampolineSum(100000) console.timeEnd("run")
Safari 12.1.2的结果#4[调试]运行:33.693ms
[Debug]运行:24.564ms
[调试]运行:25.313ms
[调试]运行:23.262ms
[调试]运行:24.848ms
[Debug]运行:23.909ms
[Debug]运行:24.248ms
[调试]运行:32.416ms
[调试]运行:24.090ms
[调试]运行:23.986ms
Google Chrome 78.0.3892.0的结果#4运行:40.73681640625ms
运行:33.955078125ms
运行:40.907958984375ms
运行:37.693115234375ms
运行:28.929931640625ms
运行:30.7548828125ms
运行:29.720947265625ms
运行:40.8310546875ms
运行:31.5830078125ms
运行:30.712890625ms
运行:30.162841796875ms
运行:31.56103515625ms
根据结果,我们可以看到装饰器,尽管它可以避免堆栈溢出错误,但是它的工作比递归和迭代版本慢。 因此,仅当您不能用迭代替换递归或在执行递归函数时担心堆栈溢出时,才应使用此方法。