JavaScript 递归性能优化
递归是编程中强大的技术,但在 JavaScript 中如果不注意优化可能会导致性能问题甚至栈溢出。以下是几种优化递归性能的方法:
1. 尾调用优化 (Tail Call Optimization, TCO)
ES6 引入了尾调用优化,但只在严格模式下有效:
'use strict';// 普通递归
function factorial(n) {if (n === 1) return 1;return n * factorial(n - 1); // 不是尾调用
}// 尾递归优化版本
function factorial(n, total = 1) {if (n === 1) return total;return factorial(n - 1, n * total); // 尾调用
}
注意:目前主流浏览器引擎对 TCO 的支持有限,Node.js 中需要开启特定 flag。
2. 使用循环替代递归
// 递归版
function fibonacci(n) {if (n <= 1) return n;return fibonacci(n - 1) + fibonacci(n - 2);
}// 循环版(性能更好)
function fibonacci(n) {let a = 0, b = 1, temp;for (let i = 0; i < n; i++) {temp = a;a = b;b = temp + b;}return a;
}
3. 记忆化 (Memoization)
缓存已计算的结果避免重复计算:
function memoizedFibonacci() {const cache = {};return function fib(n) {if (n in cache) return cache[n];if (n <= 1) return n;cache[n] = fib(n - 1) + fib(n - 2);return cache[n];};
}const fib = memoizedFibonacci();
4. 使用 trampoline 技术
将递归转换为循环执行:
function trampoline(fn) {return function(...args) {let result = fn(...args);while (typeof result === 'function') {result = result();}return result;};
}// 使用
const factorial = trampoline(function myself(n, acc = 1) {if (n <= 1) return acc;return () => myself(n - 1, n * acc);
});
5. 限制递归深度
function limitedRecursion(fn, maxDepth = 1000) {return function(...args) {if (--maxDepth < 0) throw new Error('Maximum call stack exceeded');return fn(...args);};
}
6. 使用异步递归
将递归调用拆分为多个事件循环:
function asyncRecursion(n, callback) {if (n <= 0) return process.nextTick(() => callback(1));process.nextTick(() => {asyncRecursion(n - 1, (result) => {callback(n * result);});});
}
最佳实践建议
- 优先考虑尾递归形式
- 对于深度递归考虑使用循环
- 对于重复计算使用记忆化
- 对于非常深的递归考虑 trampoline 或异步方式
- 始终设置递归终止条件
记住,递归虽然优雅,但在 JavaScript 中并不总是最高效的解决方案。根据具体场景选择最适合的方法。