【力扣】2666. 只允许一次函数调用——认识高阶函数
【力扣】2666. 只允许一次函数调用——认识高阶函数
文章目录
- 【力扣】2666. 只允许一次函数调用——认识高阶函数
- 题目
- 解决方案
- 概述
- 修改函数行为的函数的示例用途
- 节流
- 记忆化
- 时间限制
- 只允许调用一次的用例
- 转换函数所需的语法
- Rest 语法
- 参数语法
- 方法 1:Rest语法
- 方法 2:隐式返回undefined
- 方法 3:将函数绑定到上下文
题目
给定一个函数 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 是一个流行的用于此目的的包。
时间限制
假设你有一个长期重复运行的进程(例如,从数据库同步数据到内存缓存)。如果由于某种原因,异步函数从未返回值(或者需要很长时间),那么该进程将会被冻结。为了确保这种情况不会发生,你可以对异步函数进行时间限制。
只允许调用一次的用例
简要的答案是,它对于初始化逻辑非常有用。你可能有一个初始化缓存的函数(例如,将文件加载到内存),并且希望确保它只加载一次。
你可能会想,为什么不是只避免多次调用函数呢?首先,拥有这种的确定性很好。但是,还有一些情况下,你可能希望以 简便 的方式执行初始化逻辑。
let json;
function loadJsonFromFile() {
// 加载文件的逻辑
json = loadFile();
}
const loadFileOnce = once(loadJsonFromFile);
function getValue(key) {
loadFileOnce();
return json[key];
}
在此示例中,你可能只想在调用getValue
后执行加载文件的昂贵操作。例如,getValue
可能是库的一部分,可能永远不会被调用。
另一个常见用例的示例是当用户首次单击按钮时显示的欢迎消息或介绍。
const button = document.querySelector('#start-button');
button.addEventListener('click', once(function() {
displayWelcomeMessage();
}));
流行的库 lodash 包含 once 的实现。
转换函数所需的语法
Rest 语法
在 JavaScript 中,你可以使用rest
语法将所有传递的参数作为数组访问。然后,你可以使用spread
数组的语法将它们传递回函数。
function sum(...nums) {
let sum = 0;
for (const num of nums) {
sum += num;
}
return sum;
}
sum(1, 2, 3); // 6
在上面的示例中,变量nums
是 [1, 2, 3]
。
但更重要的是,你可以使用此语法转换任意函数。
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
变量。
function sum() {
let sum = 0;
for (const num of arguments) {
sum += num;
}
return sum
}
sum(1, 2, 3); // 6
arguments
是一个绑定到函数的特殊 可迭代 值。了解它对于阅读旧代码库很有用,但它目前在很大程度上已被rest
语法所取代。这其中一个原因是当你不提前记录输入值时,它可能会导致混淆。如果要在TypeScript
中使用类型注释你的函数,这一点尤其容易出问题。
方法 1:Rest语法
我们可以声明一个布尔值,用于跟踪函数是否已被调用。然后,如果函数尚未被调用,我们可以使用 rest
和spread
语法传递参数。
var once = function(fn) {
let hasBeenCalled = false;
return function(...args){
if (hasBeenCalled) {
return undefined;
} else {
hasBeenCalled = true;
return fn(...args);
}
}
};
方法 2:隐式返回undefined
如果从函数中不返回任何值,它将隐式返回undefined
。我们可以利用这一事实稍微缩短代码。
var once = function(fn) {
let hasBeenCalled = false;
return function(...args){
if (!hasBeenCalled) {
hasBeenCalled = true;
return fn(...args);
}
}
};
方法 3:将函数绑定到上下文
JavaScript 函数有时会绑定到 this
上下文。实现高阶函数的在技术上最正确的方式是确保提供的函数与返回的函数绑定到相同的上下文。
以下代码展示了如何根据绑定到的this
上下文而异地执行函数的方式。
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
应用于原型上时使用的方法。
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
。
请注意,此功能相对较小众,不是当前问题所必需的。但是,在专业实现中可能会需要使用这样的功能。
var once = function(fn) {
let hasBeenCalled = false;
return function(...args){
if (!hasBeenCalled) {
hasBeenCalled = true;
return fn.apply(this, args);
}
}
};