递归函数
当我们想计算n的阶乘的时候 我们想到的最简单的方法就是递归(空间复杂度和时间复杂度都是O(n)), 但是下面的解决方案 如果n很大的时候 函数调用栈就会爆满,从而报错
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
尾递归优化
下面代码中,如果函数g不是尾调用,函数f就需要保存内部变量m和n的值、g的调用位置等信息。但由于调用g之后,函数f就结束了,所以执行到最后一步,完全可以删除 f() 的调用记录,只保留 g(3) 的调用记录, 这就是尾递归优化
function f() {
let m = 1;
let n = 2;
return g(m + n);
}
f();
// 等同于
function f() {
return g(3);
}
f();
// 等同于
g(3);
使用尾尾递归优化 优化阶乘函数, 此时空间复杂度O(1) 时间复杂度O(n) , 目前所有浏览器只有 Safari 有支持, V8不支持尾递归优化
function factorial(n, total) {
"use strict"
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1);
蹦床函数
递归之所以导致函数调用栈爆满 主要原因就是 函数嵌套过多 导致第一层的函数无法释放,所以蹦床函数的思想就是 将嵌套的函数扁平化使得可以释放掉第一层的函数
function partial(fun, arg1) {
return fun.bind(undefined, arg1);
}
function factorial(n, total = 1) {
if (n === 1) return total;
return partial(factorial, n - 1, n * total);
}
// 所以我们手动调用第二次的时候 第一个函数在调用栈中就被删除了
factorial(3)
// function () { return factorial(3 - 1, 3) }
factorial(3)();
// function () { return factorial(2 - 1, 3 * 2) }
factorial(3)()();
// function () { return factorial(1 - 1, 1 * 3 * 2) }
上面我们手动调用factorial实现了扁平化 我们可以用代码实现它
function trampoline(f) {
return function trampolined(...args) {
let result = f.bind(null, ...args);
while (typeof result === 'function') result = result();
return result;
};
}
const factorial = trampoline(function _factorial(n, acc = 1) {
if (n <= 1) return acc;
return () => _factorial(n - 1, n * acc);
});
console.log(factorial(5));