【翻译】如何利用记忆化技术缓存JS函数结果并加速你的代码
How to use Memoize to cache JavaScript function results and speed up your code
函数是编程的组成部分,使我们的代码具有模块化和复用性。
用函数将我们的程序分割成块是很常见的,我们利用函数来执行一些有用的操作。
有时,多次调用一个函数会变的代价很高(比如计算一个数的阶乘)。缓存技术可以让我们优化这些程序,并且让他们执行的更快。
比如,我们有一个返回一个数阶乘的函数:
1 | function factorial(n) { |
如果我们要算factorial(50)
,计算机将执行这个函数 50 次,然后返回最终结果。
当factorial(50)
计算完成后,我们再来算一下factorial(51)
,可能你已经发现了,前 50 次的我们已经算过,可以不用重复计算,就像这样:
1 | factorial(51) = factorial(50) * 51; |
但是我们的原函数不会这么想,他会在每次调用时都会从头开始执行计算:
1 | factorial(51) = 51 * 50 * 49 * ... * 2 * 1 |
如果有一种方式,来让我们的factorial
函数记住他以前计算过的结果,并且用他来加速代码的执行,是不是很有用呢?
记忆化出现了,记忆化是一种让程序记住(缓存)他以前计算过的结果的方式,这里是他的定义:
在计算机科学中,记忆化(英语:memoization 而非 memorization)是一种提高程序运行速度的优化技术。通过储存大计算量函数的返回值,当这个结果再次被需要时将其从缓存提取,而不用再次计算来节省计算时间。
记忆化简单来说就是记忆,或者存储在内存里。一个记忆化程序通常更快,因为如果随后函数调用之前的一个值,我们将从缓存中获取结果,而不是执行该函数。
这里是一个记忆化函数的例子(如果你想测试一下的话,请前往CodePen)
1 | // a simple function to add something |
记忆化摘要
下面是上述代码的摘要:
memoizedAdd
返回了一个稍后调用的函数,这在 JS 里这是可能的,因为在 JS 中,函数是一类对象,可以将它们用作高顺位函数(也有译高阶函数,higher order functions)并返回另一个函数。cache
可以记录他的值,因为返回的函数存在于闭包(closures)之中- 记忆化函数应该是纯净(pure)的。一个纯净的函数对特定的输入,无论经过多少次调用,都将返回相同的输出,这让
cache
可以如我们的预期记录数据。
写下你自己的记忆化函数
前面的代码运行的不错,但是如何把我们自己的函数变成一个记忆化函数呢?
如何写下你自己的记忆化函数(CodePen):
1 | // a simple pure function to get a value adding 10 |
这个简单的memoize
函数将简单函数(simple functions)包装成一个记忆的等价物。这个函数在简单函数上运行良好,而且可以轻松调整,以适应任何数量的参数。另外一个方式是使用一些 de-facto 库,比如:
- Lodash’s
_.memoize(func, [resolver])
- 来自deckoES7 @memoize 修饰符(Decorators )
记忆递归函数
如果你尝试向memoize
函数,或者向来自 Lodash 的_.memoize
函数传递一个递归函数,结果并不会如你所料,因为其后续调用的递归函数最终将调用自身而不是记忆函数,从而不使用缓存。
所以只需确保你的递归函数也能调用记忆函数,做如下调整:CodePen
1 | // same memoize function from before |
代码中的一些需要注意的点:
factorial
函数递归调用它自己的记忆化版本。- 记忆化函数获取了之前的 factorial 值,显著提高了被重用时的计算速度
factorial(6) = 6 * factorial(5)
记忆化和缓存相同吗?
在某些程度上是的,记忆化实际上是一种缓存的特殊类型。缓存可以总体上指任何一种面向未来的存储技术(比如 HTTP 缓存(HTTP Caching)),而记忆化特指缓存函数返回值的技术。
什么时候记忆你的函数?
尽管看起来记忆化技术能被运用于所有函数,但是他也有一些使用的局限性:
- 记忆化函数应该是纯净的,这样他才可以每次对相同的输入返回相同的结果。
- 记忆是空间和速度之间的权衡,因此对于具有有限的输入范围的功能而言是重要的,以便可以更频繁地使用缓存的值
- 看起来你应该记忆(译者注:原文如此,这里应该说缓存)你的 API 调用,但是并没有必要,因为浏览器自动帮我们做了,如 HTTP 缓存(HTTP Caching)。
- 在我看来,高计算量程序是记忆化技术的最优应用之处,它可以帮助提高程序性能(阶乘和斐波那契不是真正好的现实例子)
- 如果在使用 React/Redux,可以看一看reselect,它是一个记忆选择器,用来确保你的计算计算进发生在状态树的相关部分发生变化时。
扩展阅读
- higher order functions
- closures in Javascript
- pure functions
- Lodash’s _.memoize docs and source code
- More memoization examples here and here
- reactjs/reselect
我希望本文对你有用,你现在一定更好的理解了 Javascript 中的记忆化技术。
原文载于 Medium 平台,原文地址[https://medium.freecodecamp.org/understanding-memoize-in-javascript-51d07d19430e],作者[Divyanshu Maithani](https://medium.freecodecamp.org/@divyanshu013),翻译 Mingfei Ding,转载请注明原文作者及翻译,如果版权问题请联系dmf@squirrel.tech