喂!别再要求一切了:记忆化简易指南

2025-06-09

喂!别再要求一切了:记忆化简易指南

喂,别再费力地调用函数去获取你两分钟前刚获取的数据了!你会问怎么做?其实很简单,当然是用记忆化啦。

定义

记忆化是动态规划中的一种优化技术,它涉及将昂贵的函数调用的值存储在内存中,以便当您需要再次检索这些值时,您可以更快地完成!

目标

  • 了解记忆的基本概念。
  • 识别何时应该使用记忆法。
  • 识别何时不应该使用记忆法。

先决条件

虽然不是必需的,但如果你已经了解以下知识,那么本文将会更容易理解:

概述

记忆化是一种缓存形式,它涉及将函数的返回值存储在内存中。当函数被调用时,会检查缓存对象,判断传入的输入值是否已经存在。如果存在,则返回缓存的结果。如果不存在,则进行繁重的计算,并将返回值也存储在缓存中,以便下次需要时更快地获取。

让我们看一个基本的例子...

基本示例

1. 让我们创建一个闭包

我们使用闭包来封装缓存对象,并将其初始化为一个空对象。我们还添加了一个函数来检查缓存并执行繁重的工作。

const memoizeFn = () => {
  // our cache object
  let cache = {};

  return (input) => {
    // the contents of the function which will be doing the heavy work
  }
}
Enter fullscreen mode Exit fullscreen mode

2. 让我们在闭包中创建函数

在这个例子中,我们将使用一个使输入加倍的函数,这显然不是一个要求很高的函数,但它适用于这个例子。

const memoizeFn = () => {
  let cache = {};

  return (input) => {
    const result = input * 2;

    return result;
  }
}
Enter fullscreen mode Exit fullscreen mode

3. 现在,是时候记住了

我们真正需要做的就是在内部函数中添加一个 if..else 条件,以查看缓存中是否存在该值。

const memoizeFn = () => {
  let cache = {};

  return (input) => {
    // lets log our cache here so we can see what is stored
    // when we call our function
    console.log(cache);

    // have we got the result of this input already from a previous call?
    if (cache[input]) {
     // nice, we do! No need for any heavy computation here!
      return cache[input];
    } else {
      // it’s not in our cache!
      const result = input * 2;

      // store the result in the cache so next time it is called with this input
      // we can retrieve it from our cache
      cache[input] = result;

      return result;
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

从上面的例子中可以看到,我们有一个闭包 memoizeFn,它用一个空对象初始化缓存,并返回一个计算量很大的纯函数,该函数接受一个数字作为输入。该输入用作缓存对象中的键。每次调用该函数时,都会检查缓存,看看是否已经有与输入对应的结果。

4. 让我们看看实际效果

// this invokes the first function and initialises our cache object
const doubleInput = memoizeFn();

doubleInput(10); // console log = {}
doubleInput(20); // console log = {10: 20}

// 10 is in our cache. No heavy computation needed
doubleInput(10); // console log = {10: 20, 20: 40}
Enter fullscreen mode Exit fullscreen mode

memoizeFn被调用并赋值给doubleInput变量,该变量现在可以在调用时访问缓存对象了。首先,我们传入值10 调用doubleInput函数,此时缓存对象为空,因此需要进行将该数字翻倍的繁重计算。接下来,我们将 20 作为输入,由于缓存中不存在 20,因此该值需要经过函数的繁重计算部分。最后,我们再次将 10 传递给函数,检查缓存对象是否存在键为10的值,如果存在,则从缓存中检索该值!

那么,在现实世界中我会在哪里使用它呢?

让我们看一个更实际的例子。假设你正在创建一个 SPA 社交媒体平台,用户可以拥有一个好友列表,当用户点击其中一位好友时,它会返回该用户的个人资料。我们需要调用一个 API 来返回与该个人资料相关的数据,对吗?没错。但是,如果用户在浏览网站时返回到之前访问过的个人资料,我们是否需要再次调用该 API?我们可以,或者可以使用 memoization 机制。方法如下:

const memoizeUser = () => {
  let cache = {};

  return async (userId) => {
    if (cache[userId]) {
      return cache[userId];
    }

    // it's not in our cache, we need to hit the API
    // this could take a little while...
    const data = await fetch(`https://myapi.com/users/{userId}`);

    const user = await data.json();

    cache[userId] = user;

    return user;
  }
}
Enter fullscreen mode Exit fullscreen mode

这是我们的函数,它看起来与第一个例子非常相似。接下来,让我们看看如何使用它。

// get access to the cache
const getUser = memoizeUser();

// add a click event listener to a button which gets a user’s profile
// this button will have an id of the users id that it accesses
document.querySelector('#getUserButton').addEventListener('click', async (e) => {
  const userId = e.target.id;

  // have we visited this user before? 
  const userData = await getUser(userId); 

  // rest of function which returns users profile using the
  // userData retrieved above
});
Enter fullscreen mode Exit fullscreen mode

当用户个人资料被点击时,我们会从按钮获取用户 ID,然后调用getUser函数返回用户数据。这将触发 API,除非我们之前访问过该用户个人资料时缓存了相应的数据。在这种情况下,无需调用服务器,我们可以直接从缓存中获取数据。

很简单吧?这涵盖了记忆的基础知识。

是时候更上一层楼了

如果你想真正聪明一点,你甚至可以将繁重的计算函数传递给闭包本身,它可以接受可变数量的参数。

const memoize = (fn) => {
  let cache = {};

  return (...args) => {
    // as this now takes variable arguments, we want to create a unique key
    // you would need to define this hash function yourself
    const key = hash(args);

    if (!cache[key]) {
      cache[key] = fn(...args);
    }

    return cache[key];
  }
}

// some functions we can pass to memoize
const add = (num1, num2) => num1 + num2;
const subtract = (num1, num2) => num1 - num2;

// these 2 will have different cache objects thanks to closures
const add2Numbers = memoize(add);
const subtract2Numbers = memoize(subtract);

const result1 = add2Numbers(10, 20);
const result2 = add2Numbers(20, 30);

const result3 = subtract2Numbers(10, 5);

Enter fullscreen mode Exit fullscreen mode

很酷吧?我们可以定义这个 memoize 包装器,并向其传递多个函数,每个函数接受可变数量的参数。

一些应该做的和不应该做的事

记忆什么时候有用?

  • 从 API 检索固定数据时。
  • 当执行对于给定输入可能定期重复发生的苛刻计算时。

何时不使用记忆

  • 从数据定期变化的 API 检索数据时。
  • 简单的函数调用。

总结

  • 记忆化是一种缓存形式,用于存储所需函数的结果。
  • 这是一种简单的技术,可以轻松地实现到现有的代码库中以提高性能。
  • 在处理固定数据 API 和经常发生的繁重计算功能时,记忆化很有用。
鏂囩珷鏉ユ簮锛�https://dev.to/jrdev_/oi-you-stop-requesting-everything-a-simple-guide-to-memoization-24n4
PREV
5 个很棒的 Git CLI 快捷键
NEXT
一致性在软件工程中的作用:为什么日常练习胜过周末学习