当前位置: 首页 > news >正文

【力扣】2715. 执行可取消的延迟函数

【力扣】2715. 执行可取消的延迟函数

文章目录

  • 【力扣】2715. 执行可取消的延迟函数
    • 一、题目
    • 二、解决方案
      • 1、概述
      • 2、闭包
        • (1)工作原理:
      • 3、setTimeout
        • (1)工作原理:
      • 方法 1:使用闭包
        • (1)思路:
        • (2)算法:
        • (3)实现:
        • (4)复杂度分析:
      • 方法 2:使用布尔标志
        • (1)思路:
        • (2)算法:
        • (3)实现:
        • (4)复杂度分析:
      • 4、面试提示

一、题目

给定一个函数 fn ,一个参数数组 args 和一个以毫秒为单位的超时时间 t ,返回一个取消函数 cancelFn

cancelTimeMs 的延迟后,返回的取消函数 cancelFn 将被调用。

setTimeout(cancelFn, cancelTimeMs)

最初,函数 fn 的执行应该延迟 t 毫秒。

如果在 t 毫秒的延迟之前调用了函数 cancelFn,它应该取消 fn 的延迟执行。否则,如果在指定的延迟 t 内没有调用 cancelFn,则应执行 fn,并使用提供的 args 作为参数。

示例 1

输入:fn = (x) => x * 5, args = [2], t = 20
输出:[{"time": 20, "returned": 10}]
解释:
const cancelTimeMs = 50;
const cancelFn = cancellable((x) => x * 5, [2], 20);
setTimeout(cancelFn, cancelTimeMs);取消操作被安排在延迟了 cancelTimeMs(50毫秒)后进行,这发生在 fn(2)20毫秒时执行之后。

示例 2:

输入:fn = (x) => x**2, args = [2], t = 100
输出:[]
解释:
const cancelTimeMs = 50;
const cancelFn = cancellable((x) => x**2, [2], 100);
setTimeout(cancelFn, cancelTimeMs);取消操作被安排在延迟了 cancelTimeMs(50毫秒)后进行,这发生在 fn(2)100毫秒时执行之前,导致 fn(2) 从未被调用。

示例 3:

输入:fn = (x1, x2) => x1 * x2, args = [2,4], t = 30
输出:[{"time": 30, "returned": 8}]
解释:
const cancelTimeMs = 100;
const cancelFn = cancellable((x1, x2) => x1 * x2, [2,4], 30);
setTimeout(cancelFn, cancelTimeMs);取消操作被安排在延迟了 cancelTimeMs(100毫秒)后进行,这发生在 fn(2,4)30毫秒时执行之后。

提示:

  • fn 是一个函数
  • args 是一个有效的 JSON 数组
  • 1 <= args.length <= 10
  • 20 <= t <= 1000
  • 10 <= cancelTimeMs <= 1000

二、解决方案

1、概述

在这个教程中,我们将学习如何创建一个函数,它可以在指定的延迟之后执行给定的函数,除非在延迟到期之前调用了一个取消函数 cancelFn。取消函数应该能够阻止延迟函数的执行。

2、闭包

JavaScript 中,闭包是函数和函数被声明时的词法环境的组合。词法环境包括在闭包创建时可用的变量、函数和作用域。

(1)工作原理:

当一个函数定义在另一个函数内部时,就创建了一个闭包。内部函数保留对外部函数的变量和作用域的引用。
当外部函数执行完成并返回时,闭包仍然保持其捕获的变量和作用域链的引用。
闭包允许内部函数访问和操作其外部函数的变量,即使外部函数的执行已经完成。
这种行为是可能的,因为闭包保持对其外部函数的变量和作用域链的引用,防止它们被垃圾回收。

在问题的上下文中,闭包被用于保持对定时器变量的引用,即使创建闭包的函数已经返回。这使得取消函数能够访问并修改定时器变量,有效地取消了延迟函数的执行。

3、setTimeout

setTimeoutJavaScript中的一个内置函数,它允许您在指定的延迟之后安排一个函数的执行。它可以接受无限数量的参数,但通常前两个参数总是要执行的函数和以毫秒为单位的延迟时间。

注意setTimeout实际上是一个可变参数函数,可以接受无限数量的参数。

以下是如何使用 setTimeout 的示例:

function delayedFunction() {console.log("延迟函数执行!");
}const delay = 2000;const timerId = setTimeout(delayedFunction, delay);// 在延迟到期之前取消执行:
clearTimeout(timerId);
(1)工作原理:
  • 当调用 setTimeout 时,它启动一个计时器,并设置它在指定的延迟后运行。
  • 在延迟到期后,JavaScript事件循环将指定的函数放入执行队列。
  • 一旦调用堆栈为空,函数就会被执行,其中的任何相关代码都会运行。
  • 如果在延迟到期之前取消了setTimeout函数,计划的函数将不会被执行。

在问题的上下文中,setTimeout 用于在可取消函数中安排延迟函数(fn)的执行,延迟时间为t。

总的来说,闭包和 setTimeout 在这个问题中共同工作,以创建一个可取消的延迟函数执行机制。闭包保持对 timeoutId 变量的引用,而setTimeout在指定的延迟后安排函数的执行。

在该问题的上下文中,在cancellable函数内使用 setTimeout 来调度在指定的延迟(t)之后延迟函数(fn)的执行。

方法 1:使用闭包

(1)思路:

我们可以使用 setTimeout函数安排在指定的延迟 t 之后执行延迟函数 fn。然后,我们使用 apply 方法将参数从args数组传递给 fn

此外,通过将 setTimeout 返回的timeoutId存储在 timeoutId 变量中,我们可以通过使用timeoutId来调用clearTimeout来取消延迟函数的执行。

(2)算法:
  • cancellable 函数内部,我们使用 setTimeout 安排延迟时间t后执行 fn。使用apply方法将参数数组 args 作为参数传递给 fn。另外,setTimeout 函数返回一个计时器 ID,它存储在 timeoutId 变量中。
  • 接下来,定义一个 cancelFn函数,它使用timeoutId调用 clearTimeout 以取消延迟函数的执行。
  • 最后,从 cancelFn 函数中返回 cancellable 函数。
(3)实现:
/*** @param {Function} fn* @param {Array} args* @param {number} t* @return {Function}
*/
var cancellable = function(fn, args, t) {const timeoutId = setTimeout(function() {fn.apply(null, args);}, t);const cancelFn = function() {clearTimeout(timeoutId);};return cancelFn;
};
(4)复杂度分析:

时间复杂度:O(1)
空间复杂度:O(1)

虽然可取消函数本身的时间和空间复杂度是 O(1),但需要注意,作为参数传递的函数 fn 的时间复杂度可能会有所不同。

方法 2:使用布尔标志

(1)思路:

我们可以使用一个布尔变量来决定是否允许调用函数 fn

(2)算法:
  • isCancelled 初始化为 false 的布尔变量以跟踪取消状态。
  • 使用setTimeout()安排在 t 毫秒后执行 fn,但仅在 isCancelled false时。
  • 返回一个函数,该函数将 isCancelled 的值更改为 true,从而取消 fn 的执行。如果在延迟到期之前调用该取消函数,它确保如果在延迟到期之前调用它,fn 将永远不会被调用。

虽然此方法确实防止了在调用取消函数后执行 fn,但值得注意的是,当延迟结束时,setTimeout 回调仍然会执行。这意味着即使取消,函数仍然会在 JavaScript 事件循环队列中占用一个位置。因此,从计算效率的角度来看,这种方法可能略逊色于方法 1,后者完全取消了 setTimeout

(3)实现:
/*** @param {Function} fn* @param {Array} args* @param {number} t* @return {Function}
*/
var cancellable = function(fn, args, t) {let isCancelled = false;setTimeout(() => {if(!isCancelled)fn(...args);}, t);return () => {isCancelled = true;};
};
(4)复杂度分析:

时间复杂度:O(1)
空间复杂度:O(1)

虽然可取消函数本身的时间和空间复杂度是 O(1),但需要注意,作为参数传递的函数fn 的时间复杂度可能会有所不同。

4、面试提示

  1. 你能解释在setTimeout回调中使用的apply方法的作用吗?

    apply方法用于使用提供的 args 数组作为参数调用延迟执行的函数 fn。它允许我们将args数组中的参数动态传递给 fn,以确保在最终执行fn时传递正确的参数。此外,使用 null 作为第一个参数的 apply 允许我们在不指定特定上下文(this 值)的情况下调用函数。由于延迟执行的函数不依赖于特定的上下文,因此使用 null 是合适的。

  2. 如何处理延迟函数需要特定上下文(this 值)的执行情况?

    在延迟函数依赖特定上下文(this 值)的情况下,可以使用bind方法将所需的上下文绑定到 fn。这会创建一个具有指定上下文的新函数,然后您可以将绑定的函数传递给setTimeout以进行延迟执行。

  3. 是否可以修改实现以支持在执行过程中动态更改延迟?

    是的,可以改进可取消函数的实现以支持动态更改延迟。您可以修改实现以存储计时器ID,并在设置新延迟之前使用 clearTimeout 来清除计时器。这样,您可以动态更改延迟。

  4. 有哪些适用于带延迟的可取消函数的潜在用例?

    具有延迟的可取消函数在需要在一定延迟后执行某个操作的情况下非常有用,但在可能需要在执行前取消该操作的情况下。例如,在用户界面中的场景中,用户执行某个动作后,可能需要在一定延迟后显示通知。但是,如果用户执行了使通知无关紧要的不同动作,可以取消通知的计划显示。

    另一个场景可能是在游戏环境中,需要在延迟后执行某个动作,但是中间的用户动作或游戏事件可能需要取消计划的动作。需要注意的是,这些用例与防抖或节流的情况不同,后者旨在控制函数调用的速率,而不是安排和取消可能的操作。

  5. 使用 setTimeout 来安排延迟执行的函数的执行的潜在缺点或限制是什么?

    一个限制是 setTimeout 不是精确的,可能受到系统负载等其他因素的影响。如果需要精确的定时,一些情况下会使用替代方法,如 Web Workers Web动画 API,但它们用途不同,不能总是直接替代 setTimeout

    使用performance.now()方法可以实现更精确的时间控制,它提供了子毫秒分辨率的时间戳测量,但仍然不能保证函数会在指定的延迟后运行,因为 JavaScript 是单线程的。

  6. 是否可以修改可取消函数以支持在执行过程中可以动态更改的延迟?

    是的,可以增强可取消功能以支持延迟的动态变化。您可以修改实现以存储超时 ID,并在使用更新的延迟设置新的超时之前使用 clearTimeout

  7. 你能解释一下“防抖”的概念吗?它与可取消的延迟功能有何关系?

    防抖是一种编程实践,用于确保耗时的任务不会如此频繁地触发,这在处理用户输入事件等可能频繁而快速触发事件的情况下尤其有用。防抖的核心概念是在执行函数之前设置延迟,然后在延迟到期之前每次请求函数时重置该延迟。

    虽然带有延迟的可取消函数与防抖有相似之处,因为两者都涉及可以取消的延迟函数执行,但它们并没有内在联系。可取消函数更适合动作或计算在执行前就被废弃的情况。另一方面,防抖通常不涉及显式创建可取消函数;相反,它直接在函数内清除并重置计时器。

http://www.dtcms.com/a/355944.html

相关文章:

  • 生产环境Vue组件报错:Cannot access before initialization
  • 将 agents 连接到 Elasticsearch 使用模型上下文协议 - docker
  • 前后端分离情况下,将本地vue项目和Laravel项目以及mysql放到自己的云服务器
  • 工业 5G + AI:智能制造的未来引擎
  • Mybatis-增删改查
  • 逻辑回归以及损失函数
  • 数字孪生(Digital Twin):未来产业与城市的智慧引擎
  • AI Agent从0到1:剖析Block与GSK的两种产品化落地路径
  • 容器学习04-kubernetes(k8s)
  • 海康相机开发---设备登录
  • (二分查找)Leetcode34. 在排序数组中查找元素的第一个和最后一个位置+74. 搜索二维矩阵
  • 【LInux】常用命令笔记
  • Linux之Shell编程(一)
  • 异步方法和多线程有什么区别,他们的实现逻辑是什么以及为什么异步方法: 不能和调用者在同一个类中
  • VisionPro联合编程控件导入WinFrom以及VS卡死问题
  • GCC版本和C语言标准版本的对应关系
  • 一个Demo射击小计(纯蓝图)
  • 前端学习 10-1 :验证中的UVM
  • .Net Core Web 架构(管道机制)的底层实现
  • jadx反向编译JAR包
  • 基于SQL数据库的智能问答系统设计与实现
  • Codeforces Round 1043 (Div. 3) D. From 1 to Infinity
  • 2025年9月计算机二级C++语言程序设计——选择题打卡Day9
  • 【数据分享】珠江三角洲水系地理空间全套数据集
  • x64dbg的基本调试操作 (未完,待补充)
  • 通信协议再升级,PROFINET和EtherNet IP网关迎接改造升级大挑战
  • 智慧清洁革新者:有鹿机器人自述
  • @Jenkins 介绍、部署与使用标准作业程序
  • 深入 OpenHarmony 内核:设备待机管理模块的休眠调度与资源节能技术
  • AT_abc407_f [ABC407F] Sums of Sliding Window Maximum