对于 debounce 和 throttle 这俩概念,我发现,如果非要翻译成中文的「防抖」和「节流」,我很容易就混淆了,咱也不懂什么工业节流,单从字面意思看,debounce 和 throttle 都可以用来控制函数的高频调用,节流节流,节制水流,听着都有控制高频调用的含义,那么直接从英文区别好了。
我这么区分的:bounce ,这个单词出现时,脑子里就浮现出一串波浪 ~~~,而且是有节奏有规律地上下波动~~~~那么 debounce ,首先要反抗的就是这种有规律,把有规律变成没有规律。
没有规律的话,当然要看用户怎么操作了,跟着用户的节奏摇摆,用户操作不停歇,方法不执行~~
另一方, throttle 肯定就是有规律有节奏的控制方案了,完美区分。
场景
对于 throttle ,常见的使用场景有:onscroll
,onresize
监听,除了中途有规律有节奏地控制方法调用,最后都需要再执行一次方法。
debounce 一个很普通的场景是,输入框搜索查询,监听 input 的 onchange 事件,输入内容改变时发送请求查询数据,如果每次输入改变都要发送一个请求,那对服务器来说压力很大。
对于中文搜索,我们首先可以用以下代码来优化一遍(react ):
compositionstart 和 compositionend
componentDidMount() {
const { current } = this.ref
if (current) {
current.addEventListener('compositionstart', this.handleCompositionStart)
current.addEventListener('compositionend', this.handleCompositionEnd)
}
}
handleCompositionStart = () => {
const { current } = this.ref
current.composing = true
}
handleCompositionEnd = e => {
const { current } = this.ref
current.composing = false
this.handleQueryChange(e)
}
componentWillUnmount() {
const { current } = this.ref
if (current) {
current.removeEventListener(
'compositionstart',
this.handleCompositionStart
)
current.removeEventListener('compositionend', this.handleCompositionEnd)
}
}
handleChange = e => {
const { current } = this.ref
// 中文输入还没有完成,阻止 handleChange 调用
if (current.composing) {
return
}
// 外部 handleChange 调用
this.props.handleChange(e)
}
render () {
return (
<input
onChange={this.handleChange}
placeholder="输入..."
ref={this.ref}
/>
)
}
debounce
实现 bounce:
-
要求: 1、可选择是否需要立即执行;2、停止输入后 1s 依然无输入再执行方法;
-
注意:1、this 指向;2、参数传递
function debounce(fn, options = { threshhold: 1000, immediate: false }) { if(!fn instanceof Function) { throw new TypeError('Expected a function') } let timer = null let hasCalled = false return function () { // 首次执行 if (options.immediate && !hasCalled) { fn.apply(this, arguments) hasCalled = true } else { clearTimeout(timer) timer = setTimeout(() => { // 记得加上 this 处理,以及传递 arguments fn.apply(this, arguments) }, options.threshhold) } } }
throttle:
实现 throttle :
-
要求: 1、可选择是否需要立即执行;2、每隔 1s 执行一次方法;
-
注意:1、this 指向;2、参数传递
function throttle(fn, options = { threshhold: 1000, immediate: false }) { if(!fn instanceof Function) { throw new TypeError('Expected a function') } let timer = null let hasCalled = false return function () { if (options.immediate && !hasCalled) { fn.apply(this, arguments) hasCalled = true } else { if(!timer) { timer = setTimeout(() => { fn.apply(this, arguments) timer = null }, options.threshhold) } } } }
不过,有没有一种可能,throttle 不需要最后一次调用呢?那应该怎么实现?
可以通过上次执行方法之后与这次要执行的时间差是否小与 threshhold 来判断是否是最后一次调用:
-
要求: 1、可选择是否需要立即执行;2、默认每隔 1s 执行一次;3、可选择是否需要最后执行
-
注意:1、this 指向;2、参数要传递;3、immediate 和 needEnded 不能同时 false (比如只 start 时输入了一个字符,同时设置了 false ,理解起来让人分裂...)
function throttle(fn, options = { threshhold: 1000, immediate: true, needEnded: false }) { if(!fn instanceof Function) { throw new TypeError('Expected a function') } let timer = null let hasCalled = false let previousTime = 0 return function () { if (options.immediate && !hasCalled) { fn.apply(this, arguments) hasCalled = true } else { if(!timer) { if (options.needEnded) { timer = setTimeout(() => { fn.apply(this, arguments) timer = null }, options.threshhold) } else { if(Date.now() - previousTime >= options.threshhold) { timer = setTimeout(() => { fn.apply(this, arguments) timer = null previousTime = Date.now() }, options.threshhold) } else { timer = null } } } } } }