手写 throttle(leading + trailing)Write throttle yourself (leading and trailing)
题面The problem
先把要求读完,再动手。Read every requirement before you start.
- 第一次调用立刻执行(leading)The first call runs immediately (leading)
- 窗口内的后续调用不执行Later calls inside the same window do not run
- 窗口结束时用窗口内最后一次的参数补执行(trailing)When the window closes, run once more with the arguments of the last call in it (trailing)
- 窗口过了之后再调用,又立刻执行A call after the window has passed runs immediately again
预计 20 分钟。这个数字是照「读完题就开始写、不查资料」估的 —— 第一次超时很正常,第二次要压进去。Budget 20 minutes. Overrunning on the first pass is normal; the second pass should fit.
工作区Workspace
工作区是一个真的浏览器沙箱:左边写代码,右边实时预览,下面一个「跑测试」按钮。测试和本机那套是同一批断言,转写成了浏览器里能跑的写法。The workspace is a real in-browser sandbox: edit on the left, live preview on the right, one Run button below. The assertions are the same ones that pass on a real machine, rewritten for the browser runner.
需要联网。Requires an internet connection. 打包器和 npm 依赖都在 CodeSandbox 的远程服务上(评估过程见 docs/sandpack-evaluation.md),断网这块就起不来 —— 那就照下面的命令在本机跑。The bundler and the npm packages come from CodeSandbox's remote service, so this panel needs network access.
展开讲解Walkthrough
下面是《计时两兄弟:debounce 与 throttle》那一节的原文 —— 和课程里是同一份内容,不是另写的摘要。卡住了再展开。Below is the actual text of the lesson “计时两兄弟:debounce 与 throttle” — the same content as in the course, not a rewritten summary. Expand it when you stall.
展开《计时两兄弟:debounce 与 throttle》(2 段 · 约 30 分钟)Expand “计时两兄弟:debounce 与 throttle” (2 sections · ~30 min)
完整那一节(含练习、常见错误、迁移模式)在The full lesson (with exercises, common mistakes and transfer patterns) is at 计时两兄弟:debounce 与 throttle。
debounce:把一串调用压成最后一次debounce: squeeze a burst of calls down to the last one
Write a debounce; when do you reach for it
一句话:debounce 的语义是「等你停手」—— 连续调用只在最后一次之后 delay 毫秒执行一次。 典型场景:搜索框输入(停止输入才发请求)、窗口 resize 结束后重排。
实现只有三个关键决定:
- 状态放闭包里。timer 必须在返回的函数外面 —— 放在里面每次调用都是新的,永远清不掉上一次。
- 先
clearTimeout再setTimeout。这一清一设就是「重新计时」—— debounce 的全部灵魂。 - 参数跟着 timer 走。最后一次调用的 args 被闭包捕获,之前的全部作废。
会追问:「加一个 leading 选项怎么改?」—— 进来时 timer 为空且 leading 为真,先立刻执行一次, 再设一个只负责「解锁」的 timer。追问的追问:「cancel 和 flush 有什么区别」—— cancel 丢弃挂着的调用,flush 立刻执行它。
In one line: debounce means “wait until you stop” — a burst of calls runs once, delay ms after the last one. Typical uses: search-as-you-type (fire the request when typing pauses), re-layout after window resize settles.
The implementation comes down to three decisions:
- State lives in the closure. The timer must sit outside the returned function — inside, every call would get a fresh one and nothing could ever be cleared.
clearTimeoutfirst, thensetTimeout. That clear-and-reset pair is the whole soul of debounce: the clock restarts on every call.- Arguments ride along with the timer. The closure captures the last call’s args; every earlier set is discarded.
Follow-up: “Add a leading option” — if the timer is empty and leading is on, fire immediately, then set a timer whose only job is to unlock. And the follow-up’s follow-up: “cancel vs flush” — cancel drops the pending call, flush runs it right now.
throttle:不管多密,每个窗口最多一次throttle: however dense the calls, at most one per window
Write a throttle with leading and trailing calls
一句话:throttle 的语义是「匀速放行」—— 调用再密,每 interval 毫秒最多执行一次。典型场景:滚动位置上报、 拖拽跟随、按住按钮连点。
标准版是 leading + trailing:窗口开头立刻执行一次 (用户第一下操作马上有反馈),窗口里被压掉的调用, 在窗口结束时用最后一次的参数补执行一枪 (不丢最终状态)。所以要存两样东西:上次执行的时间戳lastTime,和窗口内最后一次的参数 lastArgs。
和 debounce 的分界线一句话说死:debounce 在连续事件流里可能永远不执行(只要不停手);throttle 保证按节奏执行。 滚动进度条用 debounce 会直到停下才动 —— 那就是选错了。
会追问:「用 setTimeout 一个变量能不能写?」—— 能写 leading-only 或 trailing-only 的简版;两头都要就得 时间戳 + timer 双状态。「CSS 里有类似的东西吗」—— 没有直接等价物,但 scroll 事件配IntersectionObserver 常常能把节流的需求整个消掉。
In one line: throttle means “let it through at a steady rate” — no matter how dense the calls, it runs at most once per interval. Typical uses: reporting scroll position, drag tracking, rapid button presses.
The standard version is leading + trailing: fire once at the start of the window (the user’s first action gets instant feedback), and when the window closes, fire once more with the last suppressed call’s arguments (so the final state is not lost). That means two pieces of state: the timestamp of the last run, lastTime, and the window’s last arguments, lastArgs.
The dividing line, said once and hard: in a continuous event stream debounce may never run (as long as the stream never pauses); throttle guarantees a steady beat. A scroll progress bar built on debounce only moves when scrolling stops — that is the wrong pick.
Follow-up: “Can you write it with one setTimeout variable?” — yes for leading-only or trailing-only; both edges need the timestamp-plus-timer pair. “Anything similar in CSS?” — no direct equivalent, but scroll plus IntersectionObserver often removes the need to throttle at all.
参考答案Reference solution
提示是一级一级放的。四级看完还写不出来,再开答案门。The hints come one level at a time. If all four leave you stuck, open the answer.
这份答案在本机真跑过测试。但先确认你自己动手写过一遍 —— 读懂答案和写出答案是两种能力,考场上考的是后一种。This answer really was run here and its tests passed. But write it yourself first — reading an answer and producing one are two different skills, and the exam tests the second.