记忆化5 分钟内理解记忆化
介绍
首先,一个需要记忆的昂贵函数
为我们的 Memoizer 编写伪代码
真实代码时间
在昂贵的函数上测试我们的记忆器
仅记忆纯函数!
结论
如果您喜欢这篇文章,请给它一个💓、🦄或🔖!
记忆化(memoization)是另一个令人望而生畏的术语,但一旦你理解了它,就会变得非常直观。今天,我们来学习一下什么是记忆化!
几点说明
- 我制作了本教程的视频版本!点击此处查看。
- 如果您喜欢这篇文章,请考虑订阅我的免费每周webdev 时事通讯!
介绍
记忆化是许多编程语言中用于减少冗余且开销巨大的函数调用次数的一种优化技术。它通过根据函数的输入缓存其返回值来实现。在本文中,我们将创建一个并非最优但希望具有教育意义的 JavaScript 函数记忆器!
首先,一个需要记忆的昂贵函数
这是一个需要我们记忆的函数。它以一种非常低效的方式计算一个数的平方。
const inefficientSquare = 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);
// 1278
const start2 = new Date();
inefficientSquare(40000);
console.log(new Date() - start2);
// 1245
每次都超过一秒,哎呀!
为我们的 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];
};
};
此实现中最不理想的部分,也是我不建议在生产代码中使用它的原因,是用于在缓存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
成功了!第二次inefficientSquare
使用相同的输入进行调用时,无需重新计算;我们只是从对象中提取了缓存的值。
仅记忆纯函数!
记忆化功能很棒,但它只有在纯函数的情况下才有效。换句话说,如果函数的返回值依赖于输入以外的其他因素,那么这些输入的缓存值并不总是正确的。此外,如果函数有副作用,记忆器不会复制这些副作用,它只会返回最终返回的函数值。
结论
现在你应该已经清楚我们如何使用以及为什么使用记忆化了!虽然我们的记忆化函数并非最优,但市面上有很多第三方库可以让你做得更好。只需确保你使用的记忆化函数是纯函数即可!
文章来源:https://dev.to/nas5w/an-introduction-to-memoization-59o