在计算机领域,记忆(memoization)是主要用于加速程序计算的一种优化技术,它使得函数避免重复盐酸之前已被处理的输入,而返回已缓存的结果。
————摘自维基百科
函数可以将先前操作的结果记录在某个对象里,从而避免无谓的重复运算。JavaScript的对象和数组要实现这种优化是非常方便的,本文将实现一个递归函数,来计算Fibonacci(斐波那契数列)。
下列数组就是一个斐波那契数列,(前面相邻两项之和等于后一项的值)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
递归实现很简单,代码如下:
var fibonacci = function (n) {
console.count('fibonacci被执行的次数:')
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2)
}
document.body.innerHTML = fibonacci(10)
乍一看,一行代码搞定。但是会有些问题,这里用console.count计算了下fibonacci函数被执行的次数,结果显示:fibonacci被执行的次数: 177
可以看出它做了很多无用功,我们只调用了一次fibonacci函数,它自己却调用了自己176次。
我们可以利用闭包,使我们的函数具备记忆功能。在函数内部声明一个memory数组来储存结果,每次调用时,先判断需要的值是否存在,如果存在直接取就好了。
var fibonacci = function (n) {
var memory = [0, 1];
(function run(n) {
console.count('run执行次数')
var result = memory[n]
if (typeof memory[n] !== 'number') {
result = run(n - 1) + run(n - 2)
memory[n] = result
console.count('计算次数')
}
return result
})(n)
return memory[n]
}
document.body.innerHTML = fibonacci(10)
run执行次数: 19
计算次数: 9
控制台结果显示,计算次数显示只有9次!其余都是直接拿缓存的结果。我们可以多打印些日志,看下函数的入栈出栈顺序,加深理解。
var fibonacci = function (n) {
var memory = [0, 1];
(function run(n) {
console.group('run') // 用group分组查看打印日志,很给力~
console.log('memory[%d]: ', n, memory[n])
var result = memory[n]
if (typeof memory[n] !== 'number') {
result = run(n - 1) + run(n - 2)
memory[n] = result
console.log(`%c计算结果 ${result}`, 'color: red;')
console.count('计算次数')
} else {
console.log('直接拿到结果', result)
}
console.log('memory', memory)
console.groupEnd()
return result
})(n)
return memory[n]
}
打印结果: