EP064. “输入防抖与 setTimeout 倒数”
🔒 登录后可标记已读开始响应「在搜索框里打字」这个事件。目标是不要用户每按一个字母就立刻发请求(比如打「math」的第一个字母 M 就去查数据库没意义),而是等用户停止打字大约 2000 毫秒之后才真正执行查询逻辑——也就是「防抖」(debounce)的思路。这一讲先用 setTimeout/clearTimeout 把这套「等待 + 重新计时」的机制搭起来,用一条 console.log 占位测试,还没有真的发请求或渲染结果,这些留到下一讲 EP065。
涉及文件
wp-content/themes/fictional-university-theme/src/modules/Search.js(修改)
代码实现
// wp-content/themes/fictional-university-theme/src/modules/Search.js
import $ from "jquery"
class Search {
constructor() {
this.openButton = $(".js-search-trigger")
this.closeButton = $(".search-overlay__close")
this.searchOverlay = $(".search-overlay")
this.searchField = $("#search-term") // 新增:选取搜索输入框
this.events()
this.isOverlayOpen = false
this.typingTimer // 新增:只声明属性,暂不赋值,用来存放计时器
}
events() {
this.openButton.on("click", this.openOverlay.bind(this))
this.closeButton.on("click", this.closeOverlay.bind(this))
$(document).on("keydown", this.keyPressDispatcher.bind(this))
this.searchField.on("keydown", this.typingLogic.bind(this)) // 新增:监听输入框的按键
}
// 新增:处理「打字」这件事的防抖逻辑
typingLogic() {
clearTimeout(this.typingTimer)
this.typingTimer = setTimeout(function () {
console.log("This is a timeout test.")
}, 2000)
}
keyPressDispatcher(e) {
if (e.keyCode == 83 && !this.isOverlayOpen) {
this.openOverlay()
}
if (e.keyCode == 27 && this.isOverlayOpen) {
this.closeOverlay()
}
}
openOverlay() {
this.searchOverlay.addClass("search-overlay--active")
$("body").addClass("body-no-scroll")
console.log("our open method just ran!")
this.isOverlayOpen = true
}
closeOverlay() {
this.searchOverlay.removeClass("search-overlay--active")
$("body").removeClass("body-no-scroll")
console.log("our close method just ran!")
this.isOverlayOpen = false
}
}
export default Search
要点:
this.searchField选取的是footer.php里那个id="search-term"的<input>(overlay 的搜索框),这个 id 在 EP061 写 overlay HTML 时就已经加好了,这一讲直接拿来用。- 监听打字用的是这个输入框专属的
keydown事件(跟 EP063 那个绑在$(document)上、管全局快捷键的keydown是两条不同的事件绑定,互不冲突)。 - 防抖的核心写法:
- 每次按键先
clearTimeout(this.typingTimer)——不管之前有没有在倒数,先取消掉。 - 再重新
this.typingTimer = setTimeout(fn, 2000)——重新开始一个 2000 毫秒的倒数。 - 这样只要用户持续在打字(两次按键间隔小于 2000ms),前一个倒数会不断被取消重来,只有真正停顿超过 2000ms 才会让
setTimeout里的函数执行一次。 this.typingTimer必须是 constructor 里声明好的属性(而不是方法内部的局部变量),这样每次调用typingLogic才能拿到「上一次」设的那个计时器 ID 去清除,不然每次按键都会创建一个全新、互相独立的计时器,最终会全部各自触发一次(transcript 演示过:如果不清理,打「biology」7 个字母最终会触发 7 次回调,完全没达到防抖的目的)。.bind(this)依然要加在typingLogic上,否则方法内部this会指向搜索输入框元素而不是 Search 对象。
- 每次按键先
- 目前
setTimeout里执行的还只是console.log("This is a timeout test."),是纯粹的占位测试,真正查数据库/渲染结果放到下一讲。
Hook / Function 速查
| 名称 | 类型 | 用途 |
|---|---|---|
setTimeout(fn, ms) | JS 内建函数 | 等待指定毫秒数后执行一次 fn,返回一个计时器 ID |
clearTimeout(timerId) | JS 内建函数 | 取消一个尚未执行的 setTimeout 计时器 |
this.searchField.on("keydown", handler) | jQuery | 监听某个具体元素(这里是搜索输入框)的按键事件 |
常见坑
- 如果不用
this.typingTimer这种「存在 constructor 属性里」的写法,而是每次都用一个局部变量接setTimeout的返回值,clearTimeout就清不到上一次的计时器,导致每次按键都会各自独立倒数、各自触发一次,完全起不到「等用户打完字再执行」的效果。 - 忘记
.bind(this)会导致typingLogic方法内部访问this.typingTimer等属性时出错或拿到错误的对象。
延伸 / 后续讲座会用到
下一讲 EP065 会把 setTimeout 里的 console.log 换成真正往 overlay 里渲染 HTML(先是占位文字,还没接 WordPress REST API),并加上打字瞬间就出现的 loading 动画。真正连上 WordPress 数据库、返回真实搜索结果的逻辑要到 Section 14 才开始。
Sources
Udemy:
- Become a WordPress Developer: Unlocking Power With Code — Section 13, EP064