搞明白防抖与节流
防抖
防抖在日常项目开发的时候还是很重要的,用于防止函数的重复触发,节省性能开支。
比如监听用户的input输入进行api查询,由于input事件每次改变输入的内容都会触发,但是一般来说,只有最后一次input才有效,所以会经常采用防抖的形式来触发。
防抖:在一定时间内重复触发之后只会生效最后一次。
如何写一个防抖呢,我们慢慢来
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function() {
console.log(`触发了:${Date.now()}`, this);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(fn, delay)
}
} <
/script>
创建一个debounce
函数,函数内新增一个timer
变量,用于存放定时器,然后抛出一个新函数,函数里面判断,如果定时器存在,就先清除定时器,重新赋值一个定时器。
这样一个基本的雏形就有了,剩下的就是要考虑函数this,以及参数的问题了。
this指向
函数内运行的函数,其this指向window对象,如果在严格模式下,this就是undefined。
function fn1() {
function fn2() {
console.log(this)
}
fn2();
}
fn1(); //window,严格模式下undefined
但是我们的防抖,是将函数return出去,所以,按照防抖的写法,return出去的函数直接绑定再input对象上,那么它的this指向的就是input本身。
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function() {
console.log(`触发了:${Date.now()}`, this);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function() {
console.log(this); //input
if (timer) clearTimeout(timer);
timer = setTimeout(fn, delay)
}
}
< /script>
但是我们实际的函数fn却是被setTimeout
调用,setTimeout存在于window对象上,fn通过作用域链往上查询this,最终找到的是window,this就等于window。
在函数中,this的指向完全取决于函数调用的位置;谁调用它,他就指向调用它的那个对象。
那么,既然能在return的函数中正确的获取到this,那么我们就可以有很多种方式让fn绑定到正确的this上。
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function() {
console.log(`触发了:${Date.now()}`, this);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function() {
if (timer) clearTimeout(timer);
timer = setTimeout(fn.bind(this), delay)
}
}
</script>
我们可以用bind或者call,apply的方式改写this指向。
你甚至可以看到防抖中这种写法:
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function() {
console.log(`触发了:${Date.now()}`, this);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function() {
let that = this
if (timer) clearTimeout(timer);
timer = setTimeout(()=>{
fn.call(that);
}, delay)
}
} <
/script>
利用that变量保存,然后在箭头函数里面用call这种方式,事实上其实没必要的,箭头函数本身没有this,他的this将是声明它时作用域链中的this,那么this也会是input,因为他会找到return function它的this。
现在this,问题我们高定了,剩下的就是参数问题了。
参数
事件绑定,一般都是会使用到event对象,甚至一些其他自定义参数,我们不能确定使用者它将使用什么参数名,多少个参数,所以,我们得有一个办法获取到函数接收的所有参数。
在es5的时候,我们会使用函数内的arguments
对象,他是一个类数组对象,里面包含了所有的参数,但是并不建议使用,不过我也是道听途说,说是会影响性能。既然都es6了,可以放弃了。
现在es6的话,我们可以利用函数的reset
参数,将所有参数保存到数组中去。
function fn(...args) {
console.log(args); //[...]
}
那么现在参数可以拿到了,如何传给fn呢,我们bind方法其实提供了第二个参数,他和call的第二个参数一样,传递参数用的,所有参数用逗号分割。所以
< script >
"use strict"
const input = document.querySelector("input");
input.addEventListener("input", debounce(function(evnet) {
console.log(`触发了:${Date.now()}`, this, evnet);
}, 300))
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(fn.bind(this,...args), delay)
}
}
</script>
一个防抖函数就完成了。
注意事项
由于防抖生成的是全新的函数,如果绑定了事件,想要关闭事件,那么就必须先将防抖生成的函数变量保存下来才能成功清除事件。
<script>
"use strict"
const input = document.querySelector("input");
const button = document.querySelector("button");
const fn = debounce(function(event) {
console.log(`触发了:${Date.now()}`, this, event);
}, 300);
input.addEventListener("input", fn);
button.addEventListener("click", function(event) {
input.removeEventListener("input", fn);
});
function debounce(fn, delay) {
let timer = null;
return function(...args) {
console.log(this);
if (timer) clearTimeout(timer);
timer = setTimeout(fn.bind(this, ...args), delay)
}
}
</script>
节流
节流和防抖的处理完全不一样,节流是在一定时间内只触发一次,不管点击多少次。
用一个示意图来表明两者之间的区别:
画的比较糙,但是一眼就能看明白,节流在指定时间内触发一次,而防抖只触发最后一次。
function throttle(fn, delay) {
let timer = null;
return function(...args) {
if (timer) return;
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
};
}
当然还有一些简单的写法,比如下面这种:
function throttle(fn, delay) {
let lastTime = null;
return function(...args) {
const newTime = Date.now();
if (!lastTime || newTime - lastTime > delay) {
fn.call(this, ...args);
lastTime = newTime;
}
}
}
这种写法会有一个bug,就是最后一次没法触发,除非触发它刚好在delay的时间内,所以也不是很建议使用,是我之前自己理解的一种写法,作为经验贴在这吧!
注意事项
由于会生成新的函数,所以事件绑定的时候,记得用变量保存,方便正确清理事件。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据