高级农民
- 积分
- 4142
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2017-1-22
- 最后登录
- 1970-1-1
|
2725. Interval Cancellation
Easy
Given a function fn, an array of arguments args, and an interval time t, return a cancel function cancelFn.
After a delay of cancelTimeMs, the returned cancel function cancelFn will be invoked.
setTimeout(cancelFn, cancelTimeMs)
The function fn should be called with args immediately and then called again every t milliseconds until cancelFn is called at cancelTimeMs ms.
Example 1:
Input: fn = (x) => x * 2, args = [4], t = 35
Output:
[
{"time": 0, "returned": 8},
{"time": 35, "returned": 8},
{"time": 70, "returned": 8},
{"time": 105, "returned": 8},
{"time": 140, "returned": 8},
{"time": 175, "returned": 8}
]
Explanation:
const cancelTimeMs = 190;
const cancelFn = cancellable((x) => x * 2, [4], 35);
setTimeout(cancelFn, cancelTimeMs);
Every 35ms, fn(4) is called. Until t=190ms, then it is cancelled.
1st fn call is at 0ms. fn(4) returns 8.
2nd fn call is at 35ms. fn(4) returns 8.
3rd fn call is at 70ms. fn(4) returns 8.
4th fn call is at 105ms. fn(4) returns 8.
5th fn call is at 140ms. fn(4) returns 8.
6th fn call is at 175ms. fn(4) returns 8.
Cancelled at 190ms
Example 2:
Input: fn = (x1, x2) => (x1 * x2), args = [2, 5], t = 30
Output:
[
{"time": 0, "returned": 10},
{"time": 30, "returned": 10},
{"time": 60, "returned": 10},
{"time": 90, "returned": 10},
{"time": 120, "returned": 10},
{"time": 150, "returned": 10}
]
Explanation:
const cancelTimeMs = 165;
const cancelFn = cancellable((x1, x2) => (x1 * x2), [2, 5], 30)
setTimeout(cancelFn, cancelTimeMs)
Every 30ms, fn(2, 5) is called. Until t=165ms, then it is cancelled.
1st fn call is at 0ms
2nd fn call is at 30ms
3rd fn call is at 60ms
4th fn call is at 90ms
5th fn call is at 120ms
6th fn call is at 150ms
Cancelled at 165ms
Example 3:
Input: fn = (x1, x2, x3) => (x1 + x2 + x3), args = [5, 1, 3], t = 50
Output:
[
{"time": 0, "returned": 9},
{"time": 50, "returned": 9},
{"time": 100, "returned": 9},
{"time": 150, "returned": 9}
]
Explanation:
const cancelTimeMs = 180;
const cancelFn = cancellable((x1, x2, x3) => (x1 + x2 + x3), [5, 1, 3], 50)
setTimeout(cancelFn, cancelTimeMs)
Every 50ms, fn(5, 1, 3) is called. Until t=180ms, then it is cancelled.
1st fn call is at 0ms
2nd fn call is at 50ms
3rd fn call is at 100ms
4th fn call is at 150ms
Cancelled at 180ms
Constraints:
fn is a function
args is a valid JSON array
1 <= args.length <= 10
30 <= t <= 100
10 <= cancelTimeMs <= 500
代码解释 立即调用 ($0\text{ ms}$):原生的 setInterval(fn, t) 默认会在等待第一个 t 毫秒之后才开始第一次调用。为了满足题目要求的“立即执行”,我们直接在设置 setInterval 之前先手动调用一次 fn(...args)。 开启循环定时器:使用 setInterval 保存定时器句柄 timer。传入的回调函数每次触发时都会解构展开 args 数组并传递给 fn。 返回取消函数 (闭包):利用闭包特性,返回的函数能访问到 timer 变量。当外部调用 cancelFn() 时,执行 clearInterval(timer),就能精准终止后续的所有循环触发。
要理解 clearInterval(timer) 为什么能精准终止后续的循环,需要搞清楚 JavaScript 底层的三个核心机制:Timer ID(定时器标识符)、事件循环(Event Loop)中的定时器管理,以及 闭包(Closure)。1. setInterval 到底做了什么?(注册与发牌)当你执行这行代码时:TypeScriptconst timer = setInterval(() => { fn(...args); }, t);
JavaScript 引擎(浏览器或 Node.js)在后台做了两件事:在后台“定时器注册表”中登记任务:引擎会启动一个后台计时器,记录着:“每隔 t 毫秒,就把回调函数推进任务队列里准备执行”。返回一个独一无二的编号(Timer ID):setInterval 并不是返回那个函数本身,而是返回一个整数(或对象,统称为 ID)。比如返回了数字 42。此时变量 timer = 42。这个 timer(42)就像是你去医院看病拿到的排号单号。2. 闭包(Closure)的作用(记住单号)在 cancellable 函数里,你返回了一个匿名函数:TypeScriptreturn () => {
clearInterval(timer);
};
即使 cancellable 函数本身已经执行完毕退出了,但因为 闭包 的存在,返回给外部的那个 cancelFn 依然“死死记住”了外层作用域里的 timer 变量(比如 timer = 42)。3. clearInterval(timer) 做了什么?(销毁单号)当外部在 190ms 那一刻调用 cancelFn() 时,本质上执行的是:TypeScriptclearInterval(42);
JavaScript 引擎接收到指令后,会拿着 42 这个号去后台的“定时器注册表”里查找:引擎:“查到了,42 号定时器正在每隔 35ms 准备下一次触发。好,我现在立刻把 42 号从注册表里彻底删掉!”一旦被从注册表里注销,后台的计时器就立刻停止了。4. 结合 190ms 的具体过程看具体的时间轴:175 ms:第 6 次触发完成。此时后台注册表里的 42 号定时器开始倒计时,预订在 210 ms($175 + 35$)再次触发。190 ms:外部调用了 cancelFn() $\rightarrow$ 执行 clearInterval(42) $\rightarrow$ 42 号定时器被立即销毁。210 ms:时间到了,但引擎的定时器列表里已经没有 42 号了,所以什么都不会发生,循环彻底终止。总结clearInterval(timer) 之所以能精准终止,是因为:setInterval 给了我们一个精准的操控句柄(Timer ID);闭包把这个句柄完整地留交给了 cancelFn;clearInterval 直接从 JavaScript 引擎底层撕毁了这个句柄对应的任务。- type JSONValue = null | boolean | number | string | JSONValue[] | { [key: string]: JSONValue };
- type Fn = (...args: JSONValue[]) => void
- function cancellable(fn: Fn, args: JSONValue[], t: number): Function {
- // 1. 按照题目要求,在 0ms 时立即调用一次 fn
- fn(...args);
-
- // 2. 设置定时器,之后每隔 t 毫秒调用一次 fn
- const timer = setInterval(() => {
- fn(...args);
- }, t);
-
- // 3. 返回一个取消函数,调用时清除定时器即可
- return () => {
- clearInterval(timer);
- };
- };
- /**
- * const result = [];
- *
- * const fn = (x) => x * 2;
- * const args = [4], t = 35, cancelTimeMs = 190;
- *
- * const start = performance.now();
- *
- * const log = (...argsArr) => {
- * const diff = Math.floor(performance.now() - start);
- * result.push({"time": diff, "returned": fn(...argsArr)});
- * }
- *
- * const cancel = cancellable(log, args, t);
- *
- * setTimeout(cancel, cancelTimeMs);
- *
- * setTimeout(() => {
- * console.log(result); // [
- * // {"time":0,"returned":8},
- * // {"time":35,"returned":8},
- * // {"time":70,"returned":8},
- * // {"time":105,"returned":8},
- * // {"time":140,"returned":8},
- * // {"time":175,"returned":8}
- * // ]
- * }, cancelTimeMs + t + 15)
- */
复制代码 |
|