什么是函数防抖
概念:函数防抖(debounce),就是指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。
1、为什么需要函数防抖?
前端开发过程中,有一些事件,常见的例如,onresize,scroll,mousemove ,mousehover 等,会被频繁触发(短时间内多次触发),不做限制的话,有可能一秒之内执行几十次、几百次,如果在这些函数内部执行了其他函数,尤其是执行了操作 DOM 的函数(浏览器操作 DOM 是很耗费性能的),那不仅会浪费计算机资源,还会降低程序运行速度,甚至造成浏览器卡死、崩溃。
2、函数防抖的实现
实现原理
函数防抖的要点,是需要一个 setTimeout 来辅助实现,延迟运行需要执行的代码。如果方法多次触发,则把上次记录的延迟执行代码用 clearTimeout 清掉,重新开始计时。若计时期间事件没有被重新触发,等延迟时间计时完毕,则执行目标代码。
代码实现
//HTML部分
<div> 账户:<input type="text" id="myinput"> </div>
//JS部分
function debounce(fun, wait=1500){
//ES6语法 wait=1500 设置参数默认值,如果没有输入wait就会使用1500
let timeout = null; //闭包的知识,内层函数可以访问到这个timeout
return function(){
if(timeout){
clearTimeout(timeout) //如果存在定时器就清空
}
timeout = setTimeout(function(){fun()},wait);
}
}
function testUname(){console.log("输入结束!");}
document.getElementById('myinput').addEventListener('input',debounce(testUname,1000));
上面的代码就是防抖函数的简单运用,只要你每次输入间隔大于一秒,那么永远不会打“印输入结束!”,直到你停止输入吗,这是因为每一次的输入都会清除上一次的计时器。
优化版:this指向和arguments修复
无论是防抖还是节流,我们都要解决两个问题,this指向和arguments。 如果没有特殊指向,setInterval和setTimeout的回调函数中this的指向都是window。这是因为JS的定时器方法是定义在window下的。
从下面这个代码中可以看出,setTimeout中的this指向的是window,这显然不是我们希望的,因为我们监听的是input输入框,所以我们希望定时器里面的this指向input。
//JS部分
function debounce(fun, wait=1500){
let timeout = null;
return function(){
console.log(this); //<input id="myinput" type="text">
console.log(arguments);//Arguments { 0: input, … }
if(timeout){
//如果存在定时器就清空
clearTimeout(timeout);
}
timeout=setTimeout(function(){
console.log(this);//Window
console.log(arguments);//Arguments { … }
fun();
},wait)}
}
function testUname(){
console.log("输入结束!");
}
document.getElementById('myinput').addEventListener('input',debounce(testUname,1000))
那么有什么方法可以改变this指向吗?
1、一种简单的办法就是我们可以用参数把定时器外层函数的this和arguments保存下来。然后再通过apply改变定时器要执行的函数fun的指向。
//JS部分
function debounce(fun,wait=1500){
let timeout = null;
return function(){
let _this = this;
let arg = arguments;
if(timeout){
//如果存在定时器就清空
clearTimeout(timeout)
}
timeout = setTimeout(function(){
console.log(_this); //<input id="myinput" type="text">
console.log(arg); //Arguments { 0: input, … }
fun.apply(_this,arg); //为函数指定this并执行
},wait)
}
}
2、用ES6的箭头函数新特性: 箭头函数的 this 始终指向函数定义时的 this,而非执行时。箭头函数需要记着这句话:“箭头函数中没有 this 绑定,必须通过查找作用域链来决定其值,如果箭头函数被非箭头函数包含,则 this 绑定的是最近一层非箭头函数的 this,否则,this 为 undefined”。 所以也可以这样写:
//JS部分
function debounce(fun,wait=1500){
let timeout = null;
return function(){
if(timeout){
//如果存在定时器就清空
clearTimeout(timeout);
}
timeout=setTimeout(()=>{fun.apply(this,arguments)},wait);
}
}
“立即执行版” 和 “非立即执行版”
函数防抖其实是分为。“立即执行版” 和 “非立即执行版” 的,根据字面意思就可以发现他们的差别,所谓立即执行版就是
触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
而 “非立即执行版” 指的是 触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
这样,在限制函数频繁执行的同时,可以减少用户等待反馈的时间,提升用户体验。
在原来基础上,添加一个是否立即执行的功能。
\**
* @desc 函数防抖---“立即执行版本” 和 “非立即执行版本” 的组合版本
* @param func 需要执行的函数
* @param wait 延迟执行时间(毫秒)
* @param immediate---true 表立即执行,false 表非立即执行
**/
function debounce(func,wait,immediate) {
let timer;
return function () {
let context = this;
let args = arguments;
if (timer) clearTimeout(timer);
//存在定时器就清空
if (immediate) {
//立即执行
timer = setTimeout(function(){ func.apply(context, args) }, wait);
} else {
//非立即执行
var callNow = !timer; //第一次timer为undefine,callNow=true
timer = setTimeout(() => {
//间歌时间后 重置timer为空
timer = null;},
wait)
}
if (callNow) func.apply(context, args);
}
}
3、函数防抖的使用场景
函数防抖一般用在什么情况之下呢?一般用在,连续的事件只需触发一次回调的场合。具体有:
- 搜索框搜索输入。只需用户最后一次输入完,再发送请求;
- 用户名、手机号、邮箱输入验证;
- 浏览器窗口大小改变后,只需窗口调整完后,再执行 resize 事件中的代码,防止重复渲染。