实现一个搜索功能,当输入框中的文字改变时,就去请求结果。
一开始是这样写的:
<input type="text" onkeydown={search} />
search = () => {
requestSearchResult();
}
但存在很大的问题,仅输入"数学"两个字就会使得keydown事件触发7次,发7次请求。
通过上图可以看出,搜索内容稍有改变就会发新的请求,导致请求过于频繁。但这些请求中真正有用的其实是最后一个,最有一个请求发出时我们已经输入完完整的搜索词,请求的结果也是我们真正想搜索的。前面的请求都是没有意义的,是对网络资源的浪费。
为了让请求不那么频繁,利用setTimeout做了一下处理:
<input type="text" onkeydown={search} />
search = () => {
setTimeout (() => {
requestSearchResult();
}, 500);
}
以上代码就是当搜索内容改变时,不是立即去请求,而是等500ms之后再去请求,这样的话这500ms之内的改变都会被当成一个改变去发起请求,减少了一定的请求数。
可以看出请求数量确实减少了,但是还是存在无意义的请求。虽然将延迟的时间调大可以更有效的减少请求数,但是会导致输入结束后等待结果的时间变长,体验很不好。
推荐的解决方案:debounce(防抖)。debounce在事件和函数执行之间加了一个控制层,来控制函数的执行次数。
var debounce = require('lodash.debounce');
<input type="text" onkeydown={search} />
// 一定要注意,debounce只执行一次,执行后的函数作为事件处理函数
GOOD:
search = debounce(requestSearchResult(), 500);
BAD:
search = () => {
debounce(requestSearchResult(), 500)();
}
使用debounce之后,它只会在最后一次改变发生后才会去执行函数,只发送一次请求。
Lodash和underscore都有debounce的API,可以直接拿来用,以下是loadash.debounce使用方法:
npm i --save lodash.debounce
var debounce = require('lodash.debounce');
以下是网上看到的用es6实现的debounce代码:
export default function debounce(func, wait, immediate) {
let timeout
return function(...args) {
clearTimeout(timeout)
timeout = setTimeout(() => {
timeout = null
if (!immediate) func.apply(this, args)
}, wait)
if (immediate && !timeout) func.apply(this, [...args])
}
}
上面的代码也是可以有效减少请求数量,但是并不能将请求数量减到1,还是会有一些无谓的请求。建议用Lodash和underscore。
除了实时的输入请求外,涉及到 window.resize触发的一些请求或操作也很适合debounce大显身手。
欲了解更多,可阅读实例解析防抖动(Debouncing)和节流阀(Throttling)。