文章目录
函数防抖和节流:解决频繁性的事件触发,实现前端页面性能优化。
情景
<body>
<div class="box"></div>
<script>
let box=document.querySelector(".box");
let count=0;
function doSomething(){
box.innerHTML=count++;
}
box.onmousemove=doSomething;
</script>
</body>
当鼠标在box上移动时,就会频繁触发doSomething(),其中可能发ajax请求,如果不加以限制的话,这将会严重影响页面性能。此时,防抖与节流就可以派上用场啦~
借助underscore实现
underscore中的防抖函数:_.debounce(事件处理函数,时间间隔,是否立即执行(默认false))
-
事件处理函数doSomething是在1000ms之后才执行,如果在这段时间内再次调用,则会重新计算执行时间
-
当预定的时间内没有再次调用该函数,则执行doSomething()
<div class="box"></div>
<script src="./underscore-min.js"></script>
<script>
let box=document.querySelector(".box");
let count=0;
function doSomething(){
box.innerHTML=count++;
}
box.onmousemove=_.debounce(doSomething,1000);
</script>
节流函数:_.throttle(事件处理函数,时间间隔,{leading:boolean(第一次是否触发),trailing:boolean(最后一次是否触发)}),但是leadding和trailing中至少要有一个为true
- 持续触发mousemove事件,每隔1000ms,触发一次回调
box.onmousemove=_.throttle(doSomething,1000,{
leading:true,
trailing:false
});
防抖和节流是很常用的解决频繁事件回调(造成页面性能消耗以及卡顿现象)的方法,但是我们只使用_.debounce()和_.throttle()两个函数没必要引入整个underscore,因此我们在平常的开发中,可以自己先封装好这两个函数。
防抖(debounce)
- 防抖和节流函数都是返回一个函数
应用场景
- scroll事件滚动触发(滚动完毕再执行)
- 搜索框输入查询(输入完毕才发送请求)
- 表单验证(输入完后才验证)
- 按钮提交(连续点击按钮)
- 浏览器窗口缩放(resize事件)
基本实现
- 注意处理doSomething()中的this指向和event对象
function doSomething(e){
console.log(this);//this指向Window
console.log(e);//undefined
box.innerHTML=count++;
}
function debounce(func,time){
let timeout;
return function(){
let that=this;//this指向当前dom元素
let args=arguments;//事件对象
if(timeout){
clearTimeout(timeout);
}
timeout=setTimeout(()=>{
//改变doSomething()中的this指向,传入event对象
func.apply(that,args);
},time);
}
}
box.onmousemove=debounce(doSomething,1000);
立即执行
debounce函数的第三个参数immediate,控制是否立即执行,默认为false,当前还需要处理当immediate为true的情况
当immediate为true的时候,由于immediate的值一直都是true,因此我们定义一个变量flag(!timeout)来标记是否立即调用回调函数,在时间间隔内,将timeout置为null,flag为true的时候立即调用func函数
function debounce(func,time,immediate){
let timeout;
return function(){
let that=this;
let args=arguments;
if(timeout){
clearTimeout(timeout);
}
if(immediate){
let flag=!timeout;
timeout=setTimeout(()=>{
timeout=null;
},time);
if(flag){
func.apply(that,args);
}
}else{
timeout=setTimeout(()=>{
func.apply(that,args);
},time);
}
}
}
box.onmousemove=debounce(doSomething,1000,true);
返回值和取消操作
有时候需要用到回调函数的返回值,可以在debounce函数中定义一个变量保存func()的返回值,然后在最后返回
有时候需要在等待时间内取消事件回调,可以将要返回的函数赋值给一个变量,给函数对象添加一个cancel方法,清除定时器
——完整版
<!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>
.box{
width: 100%;
height: 200px;
line-height: 200px;
text-align: center;
font-size: 50px;
background-color: blueviolet;
color: #fff;
}
</style>
</head>
<body>
<div class="box"></div>
<button id="btn">取消</button>
<script src="./underscore-min.js"></script>
<script>
let box=document.querySelector(".box");
let btnObj=document.querySelector("#btn");
let count=0;
function doSomething(e){
box.innerHTML=count++;
}
function debounce(func,time,immediate){
let timeout;
let res;
let debounced=function(){
let that=this;
let args=arguments;
if(timeout){
clearTimeout(timeout);
}
if(immediate){
let flag=!timeout;
timeout=setTimeout(()=>{
timeout=null;
},time);
if(flag){
res=func.apply(that,args);
}
}else{
timeout=setTimeout(()=>{
res=func.apply(that,args);
},time);
}
return res;
}
debounced.cancel=function(){
clearTimeout(timeout);
timeout=null;
}
return debounced;
}
let doSome=debounce(doSomething,1000);
btnObj.onclick=doSome.cancel;
box.onmousemove=doSome;
</script>
</body>
</html>
节流(throttle)
- 如果持续触发事件,每隔一段时间,触发一次
应用场景
- DOM元素的拖拽功能实现
- 射击游戏类
- 计算鼠标移动的距离
- 监听scroll滚动事件,和防抖有所不同
使用时间戳完成节流函数({leading:true,trailing:false})
第一次会触发,最后一次不会触发
思路:设置一个旧的时间戳old和一个新的时间戳now,若old和now之间的时间差大于时间间隔time则触发一次func回调。初始的时候,将old设置成0,这样鼠标刚移入的时候,时间差必然大于time,因此第一次会触发。
注意更新old的值
function throttle(func,time){
let old=0;
return function(){
let that=this;
let args=arguments;
let now=Date.now();
if(now-old>time){
func.apply(that,args);
old=now;
}
}
}
使用定时器完成节流函数({leading:false,trailing:true})
第一次不会触发,最后一次会触发
思路:利用定时器timeout判断并重新设置定时器执行func函数,鼠标移入的时候timeout没有赋值,取反为true,经过时间间隔time之后调用func函数,同时将timeout设置为null,这样,在时间间隔之后timeout为null,取反又满足条件,继续执行。因此第一次不会触发,最后一次会触发
function throttle2(func,time){
let timeout;
return function(){
let that=this;
let args=arguments;
if(!timeout){
timeout=setTimeout(()=>{
timeout=null;
func.apply(that,args);
},time);
}
}
}
合并、处理第三个参数
function throttle(func,time,options){
let old=0;
let timeout;
return function(){
let that=this;
let args=arguments;
let now=Date.now();
if(!old&&options.leading===false){
old=now;
}
if(now-old>time){
if(timeout){
clearTimeout(timeout);
timeout=null;
}
func.apply(that,args);
old=now;
}else if(!timeout&&options.trailing==true){
timeout=setTimeout(()=>{
old=Date.now();
timeout=null;
func.apply(that,args);
},time);
}
}
}
box.onmousemove=throttle(doSomething,1000,{leading:true,trailing:true});
throttle函数同样也可以像debounce函数一样处理返回值或者设置取消操作。