前言
当你进行页面请求的时候,可能会由于页面卡顿的原因导致页面没有及时进行响应,你可能会一直点一直点,这个时候就会请求多次,那么就会有很多次执行,这个时候我们应该通过防抖的方法来将执行推到最后一次,把之前的触发全部取消,从而减少执行的次数,页面的卡顿,是前端性能优化最重要的点。并且防抖函数是基于闭包的,那么下面让我们通过一个小实例来介绍一下怎么实现防抖呢?
正文
- 定义一个container容器,
- 为其绑定一个鼠标移动事件,实现每次在container容器移动鼠标的时候,其innerHTML的值进行加一,通过定义一个变量count来实现
<!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;
color: aliceblue;
background-color: #bfa;
font-size: 30px;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
var count = 1;
var container = document.getElementById('container');
// 事件处理函数
function getUserAction(){
container.innerHTML = count++;
}
// DOM 0级事件
// addEventListener DOM 2 mousemove
container.onmousemove = getUserAction;
</script>
</body>
</html>
那么现在我们要为该事件写个防抖的函数,不能一直在container容器移动鼠标innerHTML的值一直加一,应该通过防抖的方法来实现,在一秒内鼠标不再移动后就实现加一,在应用中也就是通过防抖的方法来将执行推到最后一次,把之前的触发全部取消,从而减少执行的次数。
js思路:
- 通过id获取container容器,再定义一个变量count来实现container容器的innerHTML加一
- 定义一个防抖函数debounce,在该函数定义两个形参func:事件处理函数、wait:等待wait长度时间内在container容器内没有移动鼠标,就触发事件处理函数
- 在该函数内定义一个变量timeout,该变量用来获取定时器setTimeout的返回值,也就是定时器的id
- 在该函数内会返回一个函数,返回的函数在container触发鼠标移动时间时会调用(会形成闭包,因为当执行返回的函数时,即内部函数,需要调用外部函数debounce中的变量timeout,也就是当外部函数debounce执行完时,会形成一个闭包,该闭包包含变量timeout)
- 内部函数:定义两个变量context、args用于接收this和argument,这里的this指的是container容器,因为该内部函数为事件执行函数,用于控制执行的次数,也就是当鼠标在container容器内移动的时候,就触发调用了该函数,并且该事件执行函数是container调用的
- 先销毁上一次的定时器,也就是把前面所有的触发鼠标移动事件的请求全部清除,再调用setTimeout函数把返回的定时器的id赋值给timeout,定时器的时间为wait,也就是在wait时间内没有鼠标移动时间的触动了,就去调用事件处理函数getUserAction
- 在定时器函数中通过apply实现显示绑定,将context和args传递给func函数,即事件处理函数getUserAction(原来事件处理函数的this指向container,但是设置防抖函数后,this指向了window,所有通过apply来指向container)
<script>
var count = 1;
var container = document.getElementById('container');
// 事件处理函数
function getUserAction(){
console.log(this, arguments, 'bbbbbb');
container.innerHTML = count++;
}
// DOM 0级事件
// addEventListener DOM 2 mousemove
// 减少次数?
function debounce(func, wait) {
// 闭包
var timeout;
return function (event) {
// 事件执行函数 this --> container
// 控制执行的次数
var context = this;
var args = arguments;
clearTimeout(timeout);
// func this --> 普通函数来运行 window
timeout = setTimeout(function() {
func.apply(context, args);
}, wait);// 返回定时器的id,id在timeout的自由变量中
}
}
container.onmousemove = debounce(getUserAction, 1000);
</script>
可能回答到这面试官还会继续问你,如果我想让你让鼠标移动事件立即触发一次,也就是一开始就执行事件处理函数getUserAction,你怎么实现?
- 给debounce防抖函数传入第三个形参immediate,用于判断是否立即执行,
- 在返回的函数中先判断timeout是否为undefined,如果不为undefined,就销毁之前的定时器
- 判断传入的形参immediate是否为true,为true就定义一个变量callNow,把!timeout赋值给callNow,如果timeout为undefined,也就是还没有鼠标移动时,callNow为true,如果鼠标移动了,callNow就为false
- 调用setTimeout,里面的函数执行的内容为将timeout变为null,时间为wait,也就是等待wait时间后,把倒数第二次和之前的定时器全部清除掉
- 判断callNow,如果为true,也就是还没有进行鼠标移动之前,就执行事件处理函数getUserAction,从而达到了立即执行的效果
<script>
var count = 1;
var container = document.getElementById('container');
function getUserAction(){
console.log(this, arguments, 'bbbbbb');
container.innerHTML = count++;
}
// 防抖功能函数 为了性能优化 1/60 (屏幕刷新率)
// func 是真正要执行的处理函数, this, args权力
// wait 定时器 id clear, 最后一次
// immediate 立即执行一次
function debounce(func, wait, immediate) {
// 自由变量空间
var timeout, result;
// 真正执行的函数
return function() {
// 二传手
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function() {
timeout = null;// 把倒数第二次的定时器清除掉
}, wait);
if (callNow) result = func.apply(context, args);
}else {
timeout = setTimeout(function() {
result = func.apply(context, args);
}, wait);
}
return result;
console.log(result);
}
}
container.onmousemove = debounce(getUserAction, 1000, true);
</script>
结语
手写防抖函数传入三个形参的方法你get到了吗?快点拿下面试官~