本文为学习冴羽大佬的博客时的笔记,原文:JavaScript专题之跟着underscore学防抖
先放面试题
防抖有几种?
-
根据执行时机划分:根据函数执行的时机,防抖可以分为两种类型:
- 延迟执行防抖(默认方式):事件触发后,会等待设定的延迟时间,如果在这段时间内再次触发事件,则重新计时。只有在延迟时间内没有新的触发时,才会执行目标函数。
- 立即执行防抖:事件触发时,立即执行目标函数,然后在设定的延迟时间内,不允许再次执行。只有延迟时间结束后,才允许重新执行。
-
根据实现方式划分:针对不同的应用场景,防抖可以有不同的实现方式,例如:
- 基本防抖:仅处理基本的延迟执行逻辑,不考虑
this、传参和返回值等问题。 - 完整防抖:除了处理延迟执行逻辑外,还考虑
this、传参和返回值等问题,以适应更复杂的场景。 - 支持立即执行的防抖:在完整防抖的基础上,增加了立即执行的选项,使得防抖函数更灵活。
- 基本防抖:仅处理基本的延迟执行逻辑,不考虑
防抖解决什么问题?
在前端开发中会遇到一些频繁的事件触发,如下所示,为了节约运行资源,可以使用防抖、节流等方式减少执行、优化体验
- window 的 resize、scroll
- mousedown、mousemove
- keyup、keydown
不太恰当举例
想象一个搭公交场景,当公交车到站停车时,司机并不会只上一个乘客就关门发车,这样相当于每个乘客都需要单独等一辆车,非常浪费空间时间!所以正常情况下司机会等一段时间,比如十秒没有新乘客上车就发车,但是等待期间如果有新客上车,司机会重置等待时间,再等十秒。
虽然一般司机不会这样等,但这个过程就像防抖的原理:在一定时间内,只有当触发事件停止一段时间后,函数才会执行。
简易实现
function debounce(func, wait) {
var timeout;
return function () {
clearTimeout(timeout)
timeout = setTimeout(func, wait);
}
}
以上是简易的防抖实现,然而离完全实现还需要注意以下问题:
this- 传参
- 返回值
这三个问题也是封装高阶函数时的共性问题,还有两个特有问题:
- 支持立即执行
- 支持取消
下文将一个个解决以上问题
1. this
需要注意以上func执行时的this并不正确,是指向window
var name = 'window'
const obj = {
name:'obj',
getName:function(){console.log(this.name)}
}
obj.getDebounceName = debounce(obj.getName,300)
obj.getName()//obj
obj.getDebounceName()//window
可以用apply修正
function debounce(func, wait) {
var timeout;
return function () {
const context = this
clearTimeout(timeout)
timeout = setTimeout(()=>func.apply(context), wait);
}
}
2. 传参
func作为函数,肯定需要有传参功能
function debounce(func, wait) {
var timeout;
return function (...args) {
const context = this
clearTimeout(timeout)
timeout = setTimeout(()=>func.apply(context,args), wait);
}
}
3. 立即执行
以上都是基于延后执行的防抖,如果不想要延迟,想要促发时立即执行,并且停止连续操作一段时间之后才重新执行
function debounce(func, wait,immediate) {
var timeout;
return function (...args) {
const context = this
if(timeout)clearTimeout(timeout)
if(immediate){
//立即执行
const executeNow = !timeout
timeout = setTimeout(()=>timeout = null,wait)
if(executeNow)func.apply(context,args)
}else{
//原版 - 延迟执行
clearTimeout(timeout)
timeout = setTimeout(()=>func.apply(context,args), wait);
}
}
}
4. 返回值
如果不用promise的话,由于延后执行是异步的,直接返回都是undefined,所以这里就只实现立即执行的返回值
function debounce(func, wait, immediate) {
var timeout, result;
return function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
}
}
5. 取消
有时想取消等待时间,让下次触发时立即执行,可以添加一个cancel()方法
function debounce(func, wait, immediate) {
var timeout, result;
var debounced = function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,不再执行
var callNow = !timeout;
timeout = setTimeout(function(){
timeout = null;
}, wait)
if (callNow) result = func.apply(context, args)
}
else {
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
return result;
};
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
以上是学习防抖时的笔记,感谢再次冴羽大佬