记忆化 5 分钟内理解记忆化 简介 首先,记忆一个开销很大的函数 为我们的记忆器编写伪代码 真实代码时间 在开销很大的函数上测试我们的记忆器 只记忆纯函数! 结论

2025-05-27

记忆化5 分钟内理解记忆化

介绍

首先,一个需要记忆的昂贵函数

为我们的 Memoizer 编写伪代码

真实代码时间

在昂贵的函数上测试我们的记忆器

仅记忆纯函数!

结论

如果您喜欢这篇文章,请给它一个💓、🦄或🔖!

记忆化(memoization)是另一个令人望而生畏的术语,但一旦你理解了它,就会变得非常直观。今天,我们来学习一下什么是记忆化!


几点说明


介绍

记忆化是许多编程语言中用于减少冗余且开销巨大的函数调用次数的一种优化技术。它通过根据函数的输入缓存其返回值来实现。在本文中,我们将创建一个并非最优但希望具有教育意义的 JavaScript 函数记忆器!

首先,一个需要记忆的昂贵函数

这是一个需要我们记忆的函数。它以一种非常低效的方式计算一个数的平方。

const inefficientSquare = num => {
  let total = 0;
  for (let i = 0; i < num; i++) {
    for (let j = 0; j < num; j++) {
      total++;
    }
  }
  return total;
};
Enter fullscreen mode Exit fullscreen mode

我们可以使用相同的值运行此函数,每次执行都需要一段时间。

const start = new Date();
inefficientSquare(40000);
console.log(new Date() - start);
// 1278

const start2 = new Date();
inefficientSquare(40000);
console.log(new Date() - start2);
// 1245
Enter fullscreen mode Exit fullscreen mode

每次都超过一秒,哎呀!

为我们的 Memoizer 编写伪代码

在编写任何代码之前,让我们先推理一下我们的记忆器。

  • 将函数引用作为输入
  • 返回一个函数(因此可以像平常一样使用)
  • 创建某种类型的缓存来保存先前函数调用的结果
  • 将来任何时候调用该函数,如果存在则返回缓存结果
  • 如果缓存值不存在,则调用该函数并将结果存储在缓存中

真实代码时间

以下是上述伪代码概要的实现。正如介绍中提到的,这不是最优的,不应该在生产环境中使用。稍后我会解释原因!

// Takes a reference to a function
const memoize = func => {
  // Creates a cache of results
  const results = {};
  // Returns a function
  return (...args) => {
    // Create a key for results cache
    const argsKey = JSON.stringify(args);
    // Only execute func if no cached value
    if (!results[argsKey]) {
      // Store function call result in cache
      results[argsKey] = func(...args);
    }
    // Return cached value
    return results[argsKey];
  };
};
Enter fullscreen mode Exit fullscreen mode

此实现中最不理想的部分,也是我不建议在生产代码中使用它的原因,是用于在缓存JSON.stringify中创建键results。最大的问题是JSON.stringify它无法序列化某些输入,例如函数和符号(以及 JSON 中不存在的任何内容)。

在昂贵的函数上测试我们的记忆器

让我们复制我们的inefficientSquare例子,但这次我们将使用我们的记忆器来缓存结果。

const memoize = func => {
  const results = {};
  return (...args) => {
    const argsKey = JSON.stringify(args);
    if (!results[argsKey]) {
      results[argsKey] = func(...args);
    }
    return results[argsKey];
  };
};

const inefficientSquare = memoize(num => {
  let total = 0;
  for (let i = 0; i < num; i++) {
    for (let j = 0; j < num; j++) {
      total++;
    }
  }
  return total;
});

const start = new Date();
inefficientSquare(40000);
console.log(new Date() - start);
// 1251

const start2 = new Date();
inefficientSquare(40000);
console.log(new Date() - start2);
// 0
Enter fullscreen mode Exit fullscreen mode

成功了!第二次inefficientSquare使用相同的输入进行调用时,无需重新计算;我们只是从对象中提取了缓存的值。

仅记忆纯函数!

记忆化功能很棒,但它只有在纯函数的情况下才有效。换句话说,如果函数的返回值依赖于输入以外的其他因素,那么这些输入的缓存值并不总是正确的。此外,如果函数有副作用,记忆器不会复制这些副作用,它只会返回最终返回的函数值。

结论

现在你应该已经清楚我们如何使用以及为什么使用记忆化了!虽然我们的记忆化函数并非最优,但市面上有很多第三方库可以让你做得更好。只需确保你使用的记忆化函数是纯函数即可!

文章来源:https://dev.to/nas5w/an-introduction-to-memoization-59o
PREV
🔥 9 个开源仓库,助您的 SaaS 事业蒸蒸日上 🤔
NEXT
ECMAScript 2021(ES12)新功能