【力扣】2666. 只允许一次函数调用——认识高阶函数

【力扣】2666. 只允许一次函数调用------认识高阶函数

文章目录

题目

给定一个函数 fn ,它返回一个新的函数,返回的函数与原始函数完全相同,只不过它确保 fn 最多被调用一次。

  • 第一次调用返回的函数时,它应该返回与 fn 相同的结果。
  • 第一次后的每次调用,它应该返回 undefined

示例 1:

复制代码
输入:fn = (a,b,c) => (a + b + c), calls = [[1,2,3],[2,3,6]]
输出:[{"calls":1,"value":6}]
解释:
const onceFn = once(fn);
onceFn(1, 2, 3); // 6
onceFn(2, 3, 6); // undefined, fn 没有被调用

示例 2:

复制代码
输入:fn = (a,b,c) => (a * b * c), calls = [[5,7,4],[2,3,6],[4,6,8]]
输出:[{"calls":1,"value":140}]
解释:
const onceFn = once(fn);
onceFn(5, 7, 4); // 140
onceFn(2, 3, 6); // undefined, fn 没有被调用
onceFn(4, 6, 8); // undefined, fn 没有被调用

提示:

  • calls 是一个有效的 JSON 数组
  • 1 <= calls.length <= 10
  • 1 <= calls[i].length <= 100
  • 2 <= JSON.stringify(calls).length <= 1000

解决方案

概述

此问题要求你编写一个修改函数行为的函数,使它只能被调用一次。这是一个 高阶函数 的示例。

修改函数行为的函数的示例用途

修改或扩展函数行为的函数在 JavaScript 中非常常见,也是函数式编程的关键概念之一。充分理解它们对于成为高效的开发人员至关重要。

它们非常有用于编写优雅、可复用的代码,并且具有各种用途,其中一些将进行讨论。

节流

假设你正在实现一个搜索字段。每次键入字符时查询数据库以获取结果可能会对数据库和用户界面产生不必要的负载。为了防止这种情况,你可以使用一种称为 节流 的技术。通过节流负责向数据库发送请求的函数,我们可以确保每秒只发送有限数量的请求,从而优化系统性能。

记忆化

一种常见的优化方法是不两次使用相同的输入调用 纯函数 。相应的,你可以通过返回以前缓存的结果来避免重复计算。这也是动态规划中的重要概念。通过在函数上调用memoize()可以获得这种优化。memoizee是一个流行的用于此目的的包。

时间限制

假设你有一个长期重复运行的进程(例如,从数据库同步数据到内存缓存)。如果由于某种原因,异步函数从未返回值(或者需要很长时间),那么该进程将会被冻结。为了确保这种情况不会发生,你可以对异步函数进行时间限制。

只允许调用一次的用例

简要的答案是,它对于初始化逻辑非常有用。你可能有一个初始化缓存的函数(例如,将文件加载到内存),并且希望确保它只加载一次。

你可能会想,为什么不是只避免多次调用函数呢?首先,拥有这种的确定性很好。但是,还有一些情况下,你可能希望以 简便 的方式执行初始化逻辑。

javascript 复制代码
let json;
function loadJsonFromFile() {
  // 加载文件的逻辑
  json = loadFile();
}

const loadFileOnce = once(loadJsonFromFile);

function getValue(key) {
  loadFileOnce();
  return json[key];
}

在此示例中,你可能只想在调用getValue后执行加载文件的昂贵操作。例如,getValue 可能是库的一部分,可能永远不会被调用。

另一个常见用例的示例是当用户首次单击按钮时显示的欢迎消息或介绍。

javascript 复制代码
const button = document.querySelector('#start-button');
button.addEventListener('click', once(function() {
  displayWelcomeMessage();
}));

流行的库 lodash 包含 once 的实现。

转换函数所需的语法
Rest 语法

在 JavaScript 中,你可以使用rest语法将所有传递的参数作为数组访问。然后,你可以使用spread数组的语法将它们传递回函数。

javascript 复制代码
function sum(...nums) {
  let sum = 0;
  for (const num of nums) {
    sum += num;
  }
  return sum;
}
sum(1, 2, 3); // 6

在上面的示例中,变量nums [1, 2, 3]

但更重要的是,你可以使用此语法转换任意函数。

javascript 复制代码
function withLogging(fn) {
  return function(...args) {
    console.log('Arguments', args);
    const result = fn(...args);
    console.log('Result', result);
    return result;
  }
}
const add = withLogging((a, b) => a + b);
add(1, 2); // 记录:Arguments [1, 2] Result 3

在此示例中,不管参数是什么或有多少个参数,都将传递给 fn

参数语法

如果使用function语法(而不是箭头函数),则可以访问 arguments 变量。

javascript 复制代码
function sum() {
  let sum = 0;
  for (const num of arguments) {
    sum += num;
  }
  return sum
}
sum(1, 2, 3); // 6

arguments 是一个绑定到函数的特殊 可迭代 值。了解它对于阅读旧代码库很有用,但它目前在很大程度上已被rest语法所取代。这其中一个原因是当你不提前记录输入值时,它可能会导致混淆。如果要在TypeScript中使用类型注释你的函数,这一点尤其容易出问题。

方法 1:Rest语法

我们可以声明一个布尔值,用于跟踪函数是否已被调用。然后,如果函数尚未被调用,我们可以使用 restspread语法传递参数。

javascript 复制代码
var once = function(fn) {
  let hasBeenCalled = false;
  return function(...args){
    if (hasBeenCalled) {
      return undefined;
    } else {
      hasBeenCalled = true;
      return fn(...args);
    }
  }
};

方法 2:隐式返回undefined

如果从函数中不返回任何值,它将隐式返回undefined。我们可以利用这一事实稍微缩短代码。

javascript 复制代码
var once = function(fn) {
  let hasBeenCalled = false;
  return function(...args){
    if (!hasBeenCalled) {
      hasBeenCalled = true;
      return fn(...args);
    }
  }
};

方法 3:将函数绑定到上下文

JavaScript 函数有时会绑定到 this 上下文。实现高阶函数的在技术上最正确的方式是确保提供的函数与返回的函数绑定到相同的上下文。

以下代码展示了如何根据绑定到的this上下文而异地执行函数的方式。

javascript 复制代码
const context = { Name: 'Alice' };

function getName() {
  return this.Name;
}
const boundGetName = getName.bind(context);

getName(); // undefined
getName.apply(context, []); // "Alice"

在上面的示例中,getName 未绑定到 context,因此返回 undefined。但是,你可以使用Function.apply方法来调用getName()并将this设置为 context。你可以在 此处 阅读有关Function.apply的更多信息。

现在演示如果你想将once应用于原型上时使用的方法。

javascript 复制代码
const Person = function(name) {
  this.name = name;
}

Person.prototype.getName = once(function() {
  return this.name;
});

const alice = new Person('Alice');
alice.getName(); // 期望输出:"Alice"

为了获得这种行为,你需要使用Function.apply方法确保getName提供了适当的上下文(在本例中是alice对象)。如果没有这样做,getName 将始终返回 undefined

请注意,此功能相对较小众,不是当前问题所必需的。但是,在专业实现中可能会需要使用这样的功能。

javascript 复制代码
var once = function(fn) {
  let hasBeenCalled = false;
  return function(...args){
    if (!hasBeenCalled) {
      hasBeenCalled = true;
      return fn.apply(this, args);
    }
  }
};
相关推荐
你挚爱的强哥33 分钟前
【sgAutocomplete_v2】自定义组件:基于elementUI的el-input组件开发的搜索输入框(支持本地保存历史搜索关键词、后台获取匹配项)
javascript·vue.js·elementui
共享家95273 小时前
链表操作:分区与回文判断
c语言·开发语言·数据结构·leetcode·链表
ClaNNEd@3 小时前
大话数据结构第二章,算法笔记
数据结构·笔记·算法
圣保罗的大教堂5 小时前
《算法笔记》9.3小节——数据结构专题(2)->树的遍历 问题 A: 树查找
算法
自信的小螺丝钉6 小时前
Leetcode 1277. 统计全为 1 的正方形子矩阵 动态规划
leetcode·矩阵·动态规划
乌云暮年7 小时前
算法刷题整理合集(四)
java·开发语言·算法·dfs·bfs
大怪v7 小时前
前端佬们,装起来!给设计模式【祛魅】
前端·javascript·设计模式
Luo_LA7 小时前
【排序算法对比】快速排序、归并排序、堆排序
java·算法·排序算法
sunly_7 小时前
Flutter:页面滚动,导航栏背景颜色过渡动画
开发语言·javascript·flutter
AI技术控7 小时前
机器学习算法实战——天气数据分析(主页有源码)
算法·机器学习·数据分析