1.先来认识下定义:
防抖:触发事件后,函数在n秒内只能执行1次,如果在n秒内又触发了时间,则会重新计算函数执行的时间。
节流:连续触发事件,但是在一定时间内只执行一次函数。
2.通过underScore实现的防抖:
🌵下面来看一个通过underscore实现的案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#container{
width: 100%;
height: 200px;
line-height: 200px;
text-align: center;
background: pink;
color:rebeccapurple;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/underscore.js/1.10.2/underscore-min.js"></script>
<script>
let count=0;
let container=document.querySelector("#container");
function dosomething(){
container.innerHTML=count++;
};
container.onmousemove=_.debounce(dosomething,2000);
</script>
</body>
</html>
- 实现了用户在移动鼠标后一定的时间后,执行某个函数,防抖的理解可以类比公交车上车的原理,只有在最后一名乘客上车后,司机才会开车。
3.实现自己的debounce函数:
🍊需要解决的问题:
- 需要解决在一定时间内执行一个函数;
- 需要解决内部的this指向问题;
- 需要解决事件对象e的指向问题;
🌵阶段一版本(归正this指向版本):
function debounce(func,wait){
let timer;
console.log("debounce中的this指向",this) //指向windows
return function(){
let context=this; //这个this指向的是id为container的html元素
clearTimeout(timer); //清理掉上次的计时器
timer= setTimeout(function(){
console.log(this) // 这里的this指向的是windows
func.apply(context); //归正指向
//或者 func.bind(context)();
},wait);
}
};
let count=0;
let container=document.querySelector("#container");
function dosomething(){
console.log("dosomething的this指向",this); //这个this指向的是id为container的html元素
container.innerHTML=count++;
};
container.onmousemove=debounce(dosomething,2000);
思路:
- 自己手写debounce防抖思路大概就是首先得接收一个func,wait,然后返回一个函数,等等,这不就是闭包么?
- debounce函数一调用的时候,执行的是里面return返回的那个函数,被返回函数的那个this指向,就是当前对象的执行者---container
- settimeout里面的func要像改变指向,那么就得得使用apply或者bind方法改变func的指向
- 需要了解的是,在settimeout中被修改的func的指向,其实就是这里面dosomething的里面的log出来的this指向。
🌵阶段二版本(加入事件并且加入第三个布尔值为参数的版本):
onmousemove是一个事件,如何在dosomething中传入事件对象参数e? 因为有时候需要用到这个事件对象e。
function debounce(func,wait,immedate){
let timer;
console.log("debounce中的this指向",this);
return function()
{
let context=this;
let arg=arguments; //js中的arguments能够获取函数的实参
clearTimeout(timer);
if(immedate){
let cowNow= !timer; //将是否立即执行和定时器timer联系起来,undefined取反为true
timer = setTimeout(()=>
{
timer=null; //将定时器在一定时间后清空
},wait);
if (cowNow)
{
console.log("我执行了么?")
func.apply(context,arg);
};
}else
{
timer= setTimeout(function(){
func.apply(context,arg);
// func.bind(context)();
},wait);
}
}
};
let count=0;
let container=document.querySelector("#container");
function dosomething(e){
console.log("dosomething的this指向",this); //这个this指向的是id为container的html元素
console.log(e)
container.innerHTML=count++;
};
container.onmousemove=debounce(dosomething,2000,true);
🌵思路:
关于事件对象e:
- 是通过js中函数的arguments来拿到实际的参数,也就是事件对象e
- 事件对象e的指向,是用过给apply传入arg参数来实现的。
关于第三个参数布尔值--是否立即执行:
- 首先可以想到,可以根据传入的布尔值immediate来做个判断,来决定是否立即执行。
- 如果是的话,那么就是得执行。
- 这里面的话,🌵思路:将是否立即执行和定时器timer联系起来,进入到被return返回函数的内部的时候,首先清理上一次的timer,immedate为true的时候,timer为undefined,undefined取反为true,这样cowNow就是true,程序继续往下走,建立一个timer的定时器,并在wait的时间后,将timer赋值为null。 这样就实现了每次只要事件一触发,就清理上次的timer。
- timer定时器通过clearTimeout清理了,但是timer依然还是一个数字(settimeout返回的是一个数字),所以连续操作执行事件的时候,并不会立即连续触发,因为timer是数字,只有在settimeout内执行的函数等待wait时间后,将timer赋值为null后,才会走下一次的执行逻辑。这个地方得好好理解下。
🌵阶段三版本(防抖函数的返回值和取消操作的实现):
function debounce(func,wait,immedate){
let timer,result;
console.log("debounce中的this指向",this)
let debounced=function()
{
let context=this;
let arg=arguments;
clearTimeout(timer);
if(immedate){
function debounce(func,wait,immedate){
let timer,result;
console.log("debounce中的this指向",this)
let debounced=function()
{
let context=this;
let arg=arguments;
clearTimeout(timer);
if(immedate){
//将是否立即执行和定时器timer联系起来,undefined取反为true
let cowNow= !timer;
// console.log(timer,"timer1")
timer = setTimeout(()=>
{
timer=null;
},wait);
// console.log(timer,"timer2");
if (cowNow)
{
console.log("我执行了么?")
result= func.apply(context,arg);
};
}else
{
timer= setTimeout(function(){
result = func.apply(context,arg);
// func.bind(context)();
},wait);
};
console.log("我是结果",result)
return result;
};
//函数也是对象,给函数对象加一个cancel的方法
debounced.cancel=function(){
//清理定时器
clearTimeout(timer);
//防止内存泄漏,将timer清空
timer=null;
}
return debounced;
};
let count=0;
let container=document.querySelector("#container");
let btn=document.querySelector("#btn");
function dosomething(e){
console.log("通过arguments获取的实参",e);
console.log("dosomething的this指向",this);
container.innerHTML=count++;
return "热爱你的热爱"
};
let dosome=debounce(dosomething,8000,false);
container.onmousemove=dosome;
btn.onclick=function(){
dosome.cancel();
}
let cowNow= !timer;
// console.log(timer,"timer1")
timer = setTimeout(()=>
{
timer=null;
},wait);
// console.log(timer,"timer2");
if (cowNow)
{
console.log("我执行了么?")
result= func.apply(context,arg);
};
}else
{
timer= setTimeout(function(){
result = func.apply(context,arg);
// func.bind(context)();
},wait);
};
console.log("我是结果",result)
return result;
};
//函数也是对象,给函数对象加一个cancel的方法
debounced.cancel=function(){
//清理定时器
clearTimeout(timer);
//防止内存泄漏,将timer清空
timer=null;
}
return debounced;
};
let count=0;
//获取container和btn元素
let container=document.querySelector("#container");
let btn=document.querySelector("#btn");
function dosomething(e){
console.log("通过arguments获取的实参",e);
console.log("dosomething的this指向",this);
container.innerHTML=count++;
return "热爱你的热爱"
};
let dosome=debounce(dosomething,8000,false);
container.onmousemove=dosome;
btn.onclick=function(){
dosome.cancel();
}