最近做做搜索功能的时候使用了防抖,简单学习后特此记录,如有偏差,欢迎指正
1.防抖函数代码
//func是需要进行防抖的函数,delay是防抖的等待时间
debounce(func, delay) {
//设定一个定时器
let timer = null;
//返回进行防抖处理的函数
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
2. 防抖原理
假定有一个方法名为foo的方法。
然后有一处代码频繁的调用foo,我们不希望foo被频繁的调用,我们只希望foo两秒才被调用一次
那么我们就可以为 foo的方法进行防抖封装为foo1,那么在每次调用foo1时都会调用上面debounce中返回的函数。
//方法foo
function foo(){
console.log('foo函数被执行')
}
//频繁调用foo
for(let i=0;i<1000;i++) {
foo();//打印1000次'foo函数被执行'
}
//----------------------------
//进行防抖后频繁调用foo1
const foo1=debounce(foo,100)
for(i=0;i<10000;i++){
foo1();//打印一次'foo函数被执行'
}
-
第一次调用foo1时,timer没有值,然后给timer设定一个定时器,将需要进行防抖的操作放入到这个定时器中延时n秒再执行。
-
n秒内当foo1方法再次被调用时,此时timer中存在上一次调用foo1的所产生的计时器,由于上一次的定时器还未到时间,所以上一次的foo还未被执行。
此时判断timer中存在计时器,然后将上一次还没执行的foo的计时器timer清除,等于说取消上一次的操作,然后将这次的foo再次放入timer中延时n秒执行 -
n秒内多次调用都会重复上一步2里面的操作,这样就保证了,在n秒内foo只会被执行一次
其实就是将需要执行的方法放入到计时器中延时执行,然后下次再调用的时候再判断计时器是否执行了,如果没有就清空计时器,再设置一个,如此循环直到计时器执行
防抖的原理大概就是这样,一般我们并不会在for循环中调用,所以上述代码只是用来演示原理构思的伪代码(具体使用大概会有问题)
3. 在vue中使用防抖函数
情景:假如我现在有一个输入框,现在需要监听输入框中的输入内容的变化,来实时的向服务器请求数据
3.1 不使用防抖
上代码!
输入框
<template>
<div id="app">
<!--输入框-->
<input type="text" v-model="inputVlaue" />
</div>
</template>
监听输入框,当输入内容发生变化,实时向服务器请求数据
watch: {
inputVlaue: function (val, oldVal) {
this.request();
},
},
模拟服务器请求数据的方法,500毫秒后从服务器请求到数据
request() {
new Promise((resolve) => {
setTimeout(() => {
resolve("我是请求到的数据");
}, 500);
}).then((res) => {
console.log(res);
this.serverData = res;
});
},
三段代码合起来是这样的
<template>
<div id="app">
<!--输入框-->
<input type="text" v-model="inputVlaue" />
</div>
</template>
<script>
export default {
name: "App",
components: {},
data() {
return {
//和输入框双向绑定
inputVlaue: "",
};
},
methods: {
//模拟网络请求,500毫秒后请求到网路
request() {
new Promise((resolve) => {
setTimeout(() => {
resolve("我是请求到的数据");
}, 500);
}).then((res) => {
console.log(res);
});
},
},
//监听输入框变化
watch: {
inputVlaue: function (val, oldVal) {
this.request();
},
},
};
</script>
此时我们在输入框输入9个字符,就向服务器发送了9次数据请求,造成资源的浪费
为了避免每次输入内容或者删除内容的时候每次都向服务器请求数据,而导致资源浪费
所以选择使用防抖来限制向服务器请求数据的频率
3. 2 使用防抖
输入框、模拟请求方法不变,添加防抖函数、并进行防抖封装
封装后的代码如下
export default {
name: "App",
components: {},
data() {
return {
inputVlaue: "",
getData: null,
};
},
//生命周期函数中对网络请求进行封装
created() {
this.getData = this.debounce(() => {
this.request();
}, 500);
},
methods: {
//防抖函数
debounce(func, delay) {
let timer = null;
return function (...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
};
},
//模拟网络请求,500毫秒后请求到网路
request() {
new Promise((resolve) => {
setTimeout(() => {
resolve("我是请求到的数据");
}, 500);
}).then((res) => {
console.log(res);
});
},
},
watch: {
inputVlaue: function (val, oldVal) {
this.getData();
},
},
};
防抖功能由于多个组件都可能用到,所以一般都封装成js文件通过导出导入的方式来使用的,此处为了方便演示,我就直接写在了methods中
上述代码,我在生命周期函数中对request请求方法进行了防抖封装,每500毫秒才向服务器请求一次数据,并在data中添加了getData属性用于存放封装后的方法。
然后输入内容后,每次发生调用的时封装后的getData方法。
此时我快速输入九个字符,可以看到只向服务器请求了一次数
因为每次输入下一个字符的时候都会将上一个输入的字符所调用方法产生的计时器清空,
输入1的时候设置一个计时器1,并把request请求方法放入计时器1中,延时500ms
输入2时,计时器1还没有执行,将计时器1删除,重新设置一个计时器2,把request请求方法放入计时器2中,延时500ms
依次类推…
直到输入到9时,清空计时器8,设置计时器9,500ms内没有再输入,然后才向服务器请求数据。
本文均为我个人理解,如有偏差,请各位多多指点
3.3 错误使用
注意防抖request我是在生命周期函数中封装到getData属性中(只执行了一次),初次使用的时候不大理解,封装到了方法中,然后直接每次都调用了该方法
错误封装代码
<script>
getData(){
this.debounce(() => {
this.request();
}, 500);
}
//监听输入框变化
watch: {
inputVlaue: function (val, oldVal) {
this.getData();
},
},
</script>
这就导致了,输入框值发生变化时每次都会调用getData()方法,每次都会调用debounce都新建一个debounce函数域
导致每次所设置计时器都在不同的debounce函数闭包中,无法访问,每次调用debounce都会将重新定义一个并timer赋值为null,所以会产生每次判断timer都是null值
(如果非要这样封装使用,那么将timer定义成全局变量。。。)
简单的理解就是封装一次,多次调用,而不是封装多次。