debounce与throttle
背景
测试同学提了个 bug,在搜索框中疯狂输入文字,不一会儿接口就报错了。这个 bug 实际上就是由于每输入一个字符时,就去请求接口引起的:
const handleSearch = () => {
// ...请求接口
}
<Input onChange={handleSearch} />
解决方法是通过 debounce 来控制输入的频率。
实际上 debounce 和 throttle 这两个概念经常是一起出现,在工作中也常常使用到。在早年接触前端的时候,项目里有 lodash,因此直接使用其提供的 debounce 和 throttle 。而在一些简单的项目里,往往不引入 lodash,这个时候就需要自己实现,于此做个记录。
debounce
debounce 也就是防抖,看看 lodash 中 debounce 的定义:
_.debounce(func, [wait=0], [options={}])
返回一个函数,该函数会距离上次调用后,延迟 wait 毫秒后调用func。
在上面的 bug 中,对原搜索方法进行防抖处理后,仅当用户停止输入 300 毫秒后,才请求接口进行搜索:
// 停止输入 300 ms 后,进行搜索
_.debounce(handleSearch, 300)
####throttle
throttle 我们称之为截流,同样看看 loads 中 throttle 的定义:
_.throttle(func, [wait=0], [options={}])
返回一个函数,该函数最多每 wait 毫秒调用一次 func。
如果对搜索方法进行截流,则无论用户输入多快,每 1 秒仅请求一次:
// 停止输入 300 ms 后,进行搜索
_.debounce(handleSearch, 1000)
手写一个 debounce
function debounce(func, wait = 0) {
let timeout
return function(...args) {
if(timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(() => func.call(this, ...args), wait)
}
}
由于 func 被当作是参数传递进来,注意上面的 func 需要绑定 this。并且在使用时如果有 this 指向,需要显式指明 this,如:
const o = {
a: 1,
b: function() {
console.log(this.a);
}
}
const c = debounce(o.b).bind(o);
对 debounce 进一步拓展,如果需要在调用时立刻执行一次,则可以:
function debounce(func, wait = 0, head) {
let timeout;
return function (...args) {
const callNow = head && !timeout;
timeout && clearTimeout(timeout);
timeout = setTimeout(() => {
if(!callNow) {
func.call(this, ...args);
}
timeout = null
}, wait);
if (callNow) {
func.call(this, ...args)
};
};
}
手写一个 throttle
function throttle(func, timeFrame = 0) {
let lastTime = 0;
return function () {
const now = new Date();
if (now - lastTime >= timeFrame) {
func.call(this, ...args);
lastTime = now;
}
};
}
what’s more: rxjs
截流和防抖很多时候是出现在事件中的,使用 rxjs 则可以使用 debounceTime 和 throttleTime:
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
const inputEle = document.getElementById('input')
const inputs = fromEvent(inputEle, 'input');
const result = inputs.pipe(debounceTime(1000));
// 如果是节流可使用 throttleTime
// const result = inputs.pipe(throttleTime(1000));
result.subscribe(x => console.log(x));
rxjs + react
如果是 react 中的事件需要防抖,那么可以通过 Subject 来实现:
import React, { useEffect } from "react";
import { Subject } from "rxjs";
import "rxjs/add/operator/debounceTime";
const value$ = new Subject().debounceTime(300);
export default function App() {
useEffect(() => {
const subscription = value$.subscribe(console.log)
return () => subscription.unsubscribe()
}, [])
const onChange = e => {
value$.next(e.target.value);
};
return (
<div>
<input onChange={onChange} />
</div>
);
}