py 清除函数缓存代码

Functions are an integral part of programming. They help add modularity and reusability to our code.

函数是编程的组成部分。 它们有助于为我们的代码增加模块化可重用性

It’s quite common to divide our program into chunks using functions which we can call later to perform some useful action.

使用函数将程序分为多个块是很常见的,稍后我们可以调用这些函数来执行一些有用的操作。

Sometimes, a function can become expensive to call multiple times (say, a function to calculate the factorial of a number). But there’s a way we can optimize such functions and make them execute much faster: caching.

有时,一个函数多次调用可能会变得昂贵(例如,一个计算数字阶乘的函数)。 但是,我们有一种方法可以优化此类功能并使它们执行得更快: 缓存

For example, let’s say we have a function to return the factorial of a number:

例如,假设我们有一个function返回数字的阶乘:

function factorial(n) {
    // Calculations: n * (n-1) * (n-2) * ... (2) * (1)
    return factorial
}

Great, now let’s find factorial(50). The computer will perform calculations and return us the final answer, sweet!

太好了,现在让我们找到factorial(50) 。 计算机将执行计算并向我们返回最终答案,亲爱的!

When that’s done, let’s find factorial(51). The computer again performs a number of calculations and gets us the result, but you might have noticed that we’re already repeating a number of steps that could have been avoided. An optimized way would be:

完成后,让我们找到factorial(51) 。 计算机再次执行许多计算并获得结果,但是您可能已经注意到,我们已经在重复一些本可以避免的步骤。 一种优化的方式是:

factorial(51) = factorial(50) * 51

But our function performs the calculations from scratch every time it’s called:

但是,每次调用时,我们的function都会从头开始执行计算:

factorial(51) = 51 * 50 * 49 * ... * 2 * 1

Wouldn’t it be cool if somehow our factorial function could remember the values from its previous calculations and use them to speed up the execution?

如果我们的factorial函数能够以某种方式记住其先前计算中的值并使用它们来加快执行速度,那会很酷吗?

In comes memoization, a way for our function to remember (cache) the results. Now that you’ve a basic understanding of what we’re trying to achieve, here’s a formal definition:

备忘录中 ,它是我们function记住(缓存)结果的一种方式。 现在,您已经对我们要实现的目标有了基本的了解,下面是一个正式的定义:

Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again

记忆是一种优化技术,主要用于通过存储昂贵的函数调用的结果并在再次出现相同的输入时返回缓存的结果来加速计算机程序

Memoizing in simple terms means memorizing or storing in memory. A memoized function is usually faster because if the function is called subsequently with the previous value(s), then instead of executing the function, we would be fetching the result from the cache.

Memoizing简单来说手段记忆或存储在存储器中。 记住的函数通常更快,因为如果随后使用先前的值调用该函数,则将执行从高速缓存中获取的结果,而不是执行该函数。

Here’s what a simple memoized function might look like (and here’s a CodePen in case you want to interact with it):

这是一个简单的记忆功能可能看起来是这样的(如果您想与之交互,这是一个CodePen )

// a simple function to add something
const add = (n) => (n + 10);
add(9);
// a simple memoized function to add something
const memoizedAdd = () => {
  let cache = {};
  return (n) => {
    if (n in cache) {
      console.log('Fetching from cache');
      return cache[n];
    }
    else {
      console.log('Calculating result');
      let result = n + 10;
      cache[n] = result;
      return result;
    }
  }
}
// returned function from memoizedAdd
const newAdd = memoizedAdd();
console.log(newAdd(9)); // calculated
console.log(newAdd(9)); // cached

(Memoization takeaways)

Some takeaways from the above code are:

以上代码的一些要点是:

  • memoizedAdd returns a function which is invoked later. This is possible because in JavaScript, functions are first class objects which lets us use them as higher order functions and return another function.
    memoizedAdd返回一个function ,稍后将调用它。 这是可能的,因为在JavaScript中,函数是一流的对象,使我们可以将它们用作高阶函数并返回另一个函数。
  • cache can remember its values since the returned function has a closure over it.
    cache可以记住它的值,因为返回的函数在其上有一个闭包 。
  • It’s essential that the memoized function is pure. A pure function will return the same output for a particular input no mater how many times it’s called, which makes the cache work as expected.
    记住的功能必须是纯净的 。 纯函数将为特定输入返回相同的输出,而不会被调用多少次,这使cache按预期工作。

编写自己的memoize功能 (Writing your own memoize function)

The previous code works fine but what if we wanted to turn any function into a memoized function?

前面的代码可以正常工作,但是如果我们想将任何功能转换为记忆功能怎么办?

Here’s how to write your own memoize function (codepen):

这是编写您自己的备忘录功能( codepen )的方法:

// a simple pure function to get a value adding 10
const add = (n) => (n + 10);
console.log('Simple call', add(3));
// a simple memoize function that takes in a function
// and returns a memoized function
const memoize = (fn) => {
  let cache = {};
  return (...args) => {
    let n = args[0];  // just taking one argument here
    if (n in cache) {
      console.log('Fetching from cache');
      return cache[n];
    }
    else {
      console.log('Calculating result');
      let result = fn(n);
      cache[n] = result;
      return result;
    }
  }
}
// creating a memoized function for the 'add' pure function
const memoizedAdd = memoize(add);
console.log(memoizedAdd(3));  // calculated
console.log(memoizedAdd(3));  // cached
console.log(memoizedAdd(4));  // calculated
console.log(memoizedAdd(4));  // cached

Now that’s great! This simple memoize function will wrap any simple function into a memoized equivalent. The code works fine for simple functions and it can be easily tweaked to handle any number of arguments as per your needs. Another alternative is to make use of some de-facto libraries such as:

现在太好了! 这个简单的memoize功能会将任何简单的function包装成一个备忘的等效项。 该代码适用于简单功能,并且可以根据需要轻松调整以处理任意数量的arguments 。 另一种选择是利用一些实际的库,例如:

  • Lodash’s _.memoize(func, [resolver]) Lodash_.memoize(func, [resolver])
  • ES7 @memoize decorators from decko ES7 @memoize来自Decor的 装饰器

(Memoizing recursive functions)

If you try passing in a recursive function to the memoize function above or _.memoize from Lodash, the results won’t be as expected since the recursive function on its subsequent calls will end up calling itself instead of the memoized function thereby making no use of the cache.

如果您尝试在递归函数传递给memoize函数高于或_.memoize从Lodash,结果就不会被因为它的后续调用将最终调用本身,而不是memoized功能从而没有用递归函数预期的cache

Just make sure that your recursive function is calling the memoized function. Here’s how you can tweak a textbook factorial example (codepen):

只要确保您的递归函数正在调用记忆函数即可。 以下是调整教科书析因示例( codepen )的方法:

// same memoize function from before
const memoize = (fn) => {
  let cache = {};
  return (...args) => {
    let n = args[0];
    if (n in cache) {
      console.log('Fetching from cache', n);
      return cache[n];
    }
    else {
      console.log('Calculating result', n);
      let result = fn(n);
      cache[n] = result;
      return result;
    }
  }
}
const factorial = memoize(
  (x) => {
    if (x === 0) {
      return 1;
    }
    else {
      return x * factorial(x - 1);
    }
  }
);
console.log(factorial(5)); // calculated
console.log(factorial(6)); // calculated for 6 and cached for 5

A few points to note from this code:

此代码需要注意几点:

  • The factorial function is recursively calling a memoized version of itself.
    factorial函数递归地调用其自身的记忆版本。
  • The memoized function is caching the values of previous factorials which significantly improves calculations since they can be reused factorial(6) = 6 * factorial(5) 记忆功能正在缓存先前阶乘的值,这可以显着改善计算,因为它们可以重复使用factorial(6) = 6 * factorial(5)

(Is memoization same as caching?)

Yes, kind of. Memoization is actually a specific type of caching. While caching can refer in general to any storing technique (like HTTP caching) for future use, memoizing specifically involves caching the return values of a function.

是的,有点。 备注实际上是一种特定类型的缓存。 虽然缓存通常可以指代任何存储技术(例如HTTP缓存 )以备将来使用,但记忆特别涉及缓存 function的返回值。

(When to memoize your functions)

Although it might look like memoization can be used with all functions, it actually has limited use cases:

尽管看起来备忘录可以与所有功能一起使用,但实际上它的用例有限:

  • In order to memoize a function, it should be pure so that return values are the same for same inputs every time
  • Memoizing is a trade-off between added space and added speed and thus only significant for functions having a limited input range so that cached values can be made use of more frequently
  • It might look like you should memoize your API calls however it isn’t necessary because the browser automatically caches them for you. See HTTP caching for more detail
    看起来您应该记住自己的API调用,但这不是必需的,因为浏览器会自动为您缓存它们。 有关更多详细信息,请参见HTTP缓存
  • The best use case I found for memoized functions is for heavy computational functions which can significantly improve performance (factorial and fibonacci are not really good real world examples)
    我发现记忆功能的最佳用例是重型计算功能 ,可以显着提高性能(阶乘和斐波那契并不是真正的好例子)
  • If you’re into React/Redux you can check out reselect which uses a memoized selector to ensure that calculations only happen when a change happens in a related part of the state tree.
    如果您正在使用React / Redux,则可以签出使用选择器的 reselect方法 ,以确保仅在状态树的相关部分发生更改时才进行计算。
(Further reading)

The following links can be useful if you would like to know more about some of the topics from this article in more detail:

如果您想更详细地了解本文中的某些主题,以下链接可能会很有用:

I hope this article was useful for you, and you’ve gained a better understanding of memoization in JavaScript :)

希望本文对您有用,并且您对JavaScript的记忆有了更好的理解:)