JavaScript备忘:方便的函数式编程

每当您编写代码时,这都意味着该代码将为您完成工作。 很多时候,这项工作的负担对我们和我们的用户都是透明的。 当它变得显而易见时,我们就会遇到性能问题。 在前端,这可能表现为滞后或残缺 。 在后端,我们可能会有较高的延迟或较慢的响应。

有时,有些方法可以选择更快的算法或更好的数据结构来解决我们的性能问题,但并非总是如此。 那么,当我们太慢但又无法加快速度时,我们该怎么办? 在JavaScript中执行此操作的一种方式称为备忘录,这是一种用于处理昂贵的纯函数的函数编程技术。

[在InfoWorld上更深入: 超越jQuery:JavaScript框架专家指南Node.js框架的完整指南10个必备JavaScript开发人员工具6个最佳JavaScript IDE10个最佳JavaScript编辑器 | 通过InfoWorld的App Dev Report新闻通讯了解编程方面的热门话题。 ]

通过备忘录,您可以将一个函数与另一个函数“包装”在一起,该函数会记住参数并返回函数调用的结果。 结果本质上是对用户透明的内存中缓存。 让我们通过简单但昂贵的功能实现备忘录来仔细了解备忘录。

关于性能改进的说明:如果您正在认真地考虑改进性能,则第一步是评估当前性能。 没有清晰的度量,您将无法识别瓶颈或验证候选解决方案。 为了这篇文章的缘故,我用JavaScript编写了一个小函数,该函数接受一个函数及其参数,并对该函数的执行进行计时。 看起来像这样:

const timeFn = (fn, args = []) => {
  const start = Date.now();
  const returnValue = fn(...args);
  const end = Date.now();
  console.log(`Execution took ${end - start} ms`);
  return returnValue;
}

我们将用于测试记忆的函数将把介于零和作为参数传入的数字之间的数字加起来。 该函数可以这样定义:

const summate = (countTo) => {
  let value = 0;
  for (let i = 0; i < countTo; i++) {
    value += i;
  }
  return value;
}

使用我们的计时器函数,我们得到以下信息:

let sum = timeFn(summate, [1000000000]);
// sum is set to 499999999067109000
// The console logs: Execution took 1064 ms

现在我们有了一个昂贵的功能,并且有了一种衡量成本的方法,让我们了解并实现该功能的备忘。

记忆化是包装函数并保存参数和返回值的一种方法,以便下次传入参数时,我们可以检查是否已经完成工作。 如果我们已经完成工作,则可以跳过繁重的工作并返回之前保存的值。

记忆仅适用于纯函数,纯函数是在给定相同参数的情况下返回相同结果且没有副作用的函数。 一个名为add函数,它带有两个参数并返回总和,这将是一个纯函数,因为每次将12作为参数时,都会得到3作为返回值。 相比之下,采用单个参数并返回参数与当前时间之和(以毫秒为单位)的函数不是纯函数。 每次将其交给1 ,由于时间总是不同,它将返回不同的值。

上面的summate函数是纯函数,因此它是用于记忆的候选函数。 我们将要记忆一个功能的步骤如下所示:

  1. 调用函数时,请查看传入的参数。
  2. 如果参数在查找表(在我们的示例中为JavaScript对象)中作为键存在,则返回该查找的值。
  3. 如果不是,请运行带有参数的函数。
  4. 以参数为键,将函数的返回值保存到我们的查询表中。
  5. 返回计算值。

上面的步骤将确保在每次使用以前见过的参数调用函数时,我们都将返回先前计算的结果并节省一些时间。 为了对我们的用户透明地完成此操作,我们将创建一个高阶函数,该函数将一个函数作为参数并返回执行上述步骤的新函数。 这是高阶函数:

let simpleMemoizer = (fn) => {
  // Set up a lookup table within a closure so we have
  // access every time our inner function is called
  const lookupTable = {};
  return (arg) => {
    // Step 1
    if(arg in lookupTable) {
      // Step 2
      return lookupTable[arg];
    } else {
      // Step 3
      let returnValue = fn(arg);
      // Step 4
      lookupTable[arg] = returnValue;
      // Step 5
      return returnValue;
    }
  }
}

然后,我们可以使用此函数包装summate函数以对其进行summate

const memoSummate = simpleMemoizer(summate);

生成的函数memoSummate可以像summate一样summate ,但是它将立即返回已经计算的结果,而不是重新计算它们。 我们可以通过运行timeFn的一些示例来验证这timeFn

timeFn(memoSummate, [100000000])
// Logs: Execution took 115 ms
// Returns: 4999999950000000
// When we call it again…
timeFn(memoSummate, [100000000])
// Logs: Execution took 0 ms
// Returns: 4999999950000000

在那里,您拥有了! 请注意,出于演示目的,此回忆器的实现非常幼稚。 它仅注意单个参数,该参数本质上必须是字符串或数字。 像lodashramda GitHub存储库中提供的那样,更复杂的备注功能将为您覆盖这些额外的情况,并提供更多通用功能。

对其他功能性编程主题的评论或要求? 继续在Twitter上进行对话: @freethejazz

From: https://www.infoworld.com/article/3388303/javascript-memoization-handy-functional-programming.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值