为什么
开发过程中会遇到一些的频繁的事件触发
增加服务器压力,导致页面卡顿,影响用户体验
提升性能,提高用户体验
- window 的 resize、scroll
- mousedown、mousemove
- keyup、keydown…
是什么
尽管触发事件,在触发事件的n秒后执行,如何在一个事件触发后的n秒内再次触发事件,以新的触发事件为准开始计时
(核心 : 清零)
怎么办
简单版 防抖函数
注意 有返回值 返回函数
//初始化定时器
let timerId = null;
//防抖函数
function debounce(fn,wait)
{
return function(){
//清除之前的计时器
clearTimeout(timerId);
//设置新的计时器
timerId = setTimeout(fn,wait)
}
}
bug
this指向
//调用的函数
function getUserAction() {
console.log(this); // 应该是container元素,但是添加防抖函数后,this指向window
container.innerHTML = count++;
};
解决
//初始化定时器
let timerId = null;
//防抖函数
function debounce(fn,wait)
{
return function(){
//获取当前的this指向
let context = this;
//清除之前的计时器
clearTimeout(timerId);
//设置新的计时器
timerId = setTimeout(()=>{
fn.apply(context);
},wait)
}
}
event对象
JavaScript 在事件处理函数中会提供事件对象 event,我们修改下 getUserAction 函数:
//调用的函数
function getUserAction(e) {
console.log(e); // 应该是mouseevent事件对象,但是添加防抖函数后,undefined
container.innerHTML = count++;
};
解决
//初始化定时器
let timerId = null;
//防抖函数
function debounce(fn,wait)
{
return function(){
//获取当前的this指向
let context = this;
//获取当前的参数
let args = arguments;
//清除之前的计时器
clearTimeout(timerId);
//设置新的计时器
timerId = setTimeout(()=>{
fn.apply(context,args);
},wait)
}
}
需求: 立刻执行
现在发现一触发事件,需要等待n秒后才执行
我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行。
//防抖函数
function debounce(fn,wait,immediate)
{
//初始化定时器
let timerId = null;
let callNow
return function(){
//获取当前的this指向
let context = this;
//获取当前的参数
let args = arguments;
//清除之前的计时器
clearTimeout(timerId);
//判断是否立即执行
if(immediate){
// 如果已经执行过,不再执行
callNow = !timerId;//每一次触发timerId都为null(会被清除),所以第一次为true,之后为false;但是这里
// 设置新的计时器
timerId = setTimeout(function(){
timerId = null;
}, wait)
//没有执行过,立即执行
if (callNow) fn.apply(context, args)
}else{
//不需要立即执行
timerId = setTimeout(()=>{
fn.apply(context,args);
},wait)
}
}
}
注意:
callNow = !timerId;
//每一次触发timerId都为null(会被清除),所以第一次为true,之后为false;但是这里不太对
暂时这样理解
第一次,timer=null,callnow =true,立即执行了函数
第二次,在代码上方清除timer的时候,由于timer=一个定时器还没有执行成功,所以清除失败,但是callnow=!timer
这条语句执行了,callnow=false,然后又更新timer,不执行函数
需求:调用函数有返回值
此时注意一点,就是 getUserAction 函数可能是有返回值的,所以我们也要返回函数的执行结果,但是当 immediate 为 false 的时候,因为使用了 setTimeout ,我们将 func.apply(context, args) 的返回值赋给变量,最后再 return 的时候,值将会一直是 undefined,所以我们只在 immediate 为 true 的时候返回函数的执行结果。
//防抖函数
function debounce(fn,wait,immediate)
{
//初始化定时器
let timerId = null;
let callNow
//函数返回值
let res;
return function(){
//获取当前的this指向
let context = this;
//获取当前的参数
let args = arguments;
//清除之前的计时器
clearTimeout(timerId);
//判断是否立即执行
if(immediate){
// 如果已经执行过,不再执行
callNow = !timerId;//每一次触发timerId都为null(会被清除),所以第一次为true,之后为false;但是这里
// 设置新的计时器
timerId = setTimeout(function(){
timerId = null;
}, wait)
//没有执行过,立即执行
if (callNow) res=fn.apply(context, args)
}else{
//不需要立即执行
timerId = setTimeout(()=>{
fn.apply(context,args);
},wait)
}
return res;
}
}
需求:自由选择取消防抖
//防抖函数
function debounce(fn,wait,immediate)
{
//初始化定时器
let timerId = null;
let callNow
//函数返回值
let res;
var debounceMy= function(){
//获取当前的this指向
let context = this;
//获取当前的参数
let args = arguments;
//清除之前的计时器
clearTimeout(timerId);
//判断是否立即执行
if(immediate){
// 如果已经执行过,不再执行
callNow = !timerId;//每一次触发timerId都为null(会被清除),所以第一次为true,之后为false;但是这里
// 设置新的计时器
timerId = setTimeout(function(){
timerId = null;
}, wait)
//没有执行过,立即执行
if (callNow) res=fn.apply(context, args)
}else{
//不需要立即执行
timerId = setTimeout(()=>{
fn.apply(context,args);
},wait)
}
return res;
}
debounceMy.cancel = function() {
clearTimeout(timerId);
console.log("取消了");
timerId = null;
};
return debounceMy;
}
完整版
let count = 1;
let container = document.getElementById('container');
//防抖函数
function debounce(fn,wait,immediate)
{
//初始化定时器
let timerId = null;
let callNow
//函数返回值
let res;
var debounceMy= function(){
//获取当前的this指向
let context = this;
//获取当前的参数
let args = arguments;
//清除之前的计时器
clearTimeout(timerId);
//判断是否立即执行
if(immediate){
// 如果已经执行过,不再执行
callNow = !timerId;//每一次触发timerId都为null(会被清除),所以第一次为true,之后为false;但是这里
// 设置新的计时器
timerId = setTimeout(function(){
timerId = null;
}, wait)
//没有执行过,立即执行
if (callNow) res=fn.apply(context, args)
}else{
//不需要立即执行
timerId = setTimeout(()=>{
fn.apply(context,args);
},wait)
}
return res;
}
debounceMy.cancel = function() {
clearTimeout(timerId);
console.log("取消了");
timerId = null;
};
return debounceMy;
}
//调用的函数
function getUserAction(e) {
console.log(e); // 应该是mouseevent事件对象,但是添加防抖函数后,undefined
container.innerHTML = count++;
return count;
};
let setUseAction = debounce(getUserAction, 1000,false);
container.onmousemove = setUseAction;
document.getElementById("button").addEventListener('click', function(){
setUseAction.cancel();
})
// container.onmousemove = debounce(getUserAction,1000);
// container.onmousemove = getUserAction;
html
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
<title>debounce</title>
<style>
#container{
width: 100%; height: 200px; line-height: 200px; text-align: center; color: #fff; background-color: #444; font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<button id="button">点击取消防抖</button>
<script src="test.js"></script>
</body>
</html>