ps:本文将从一个案例出发循序渐进,在其中你不仅能知道防抖是如何实现的,还可以学习到关于 this 、apply、arguments 等知识的具体应用。
Why?为啥要有防抖?
因为有时候频繁的事件触发是没有意义的,不仅影响性能还可能造成卡顿。
为了避免这种情况,我们需要用防抖来解决这个问题。
What? 啥是防抖?
防抖debounce:
简单来说,防抖的效果就是在一定的时间间隔内,多次触发只有一次触发产生。
How? 防抖咋用啊?
首先先从一个实例入手:
我们设置一个按钮用来切换上方文本的颜色,如下:
<h2 id="demo">hello</h2>
<button id="btn">点我</button>
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.addEventListener('click', changeColor, false);
var flag = true
function changeColor() {
if (flag) {
document.getElementById('demo').style.color = 'pink'
} else {
document.getElementById('demo').style.color = 'blue'
}
flag = !flag
}
如果你想要让用户在1秒内不能频繁切换 ,即1秒内只能给文本换一种颜色。
那么你可以利用定时器来实现,不过直接写一个定时器是不够的,因为你只能实现多次触发延时执行,而不是限制触发。
你可以使用一个变量来判断你的每次按下按钮时候是否已经触发过定时器了,如果触发过了就将原来触发但还没到1秒的定时器清除,接着重新来个1秒的定时器;如果没触发过说明你1秒内没按过,新建一个1秒的定时器就行。
这样就可以保证防抖的效果实现了,请看代码:
<h2 id="demo">hello</h2>
<button id="btn">点我</button>
<script type="text/javascript">
var btn = document.getElementById('btn');
btn.addEventListener('click', debounce(changeColor), false);
var flag = true
function changeColor() {
if (flag) {
document.getElementById('demo').style.color = 'pink'
} else {
document.getElementById('demo').style.color = 'blue'
}
flag = !flag
}
function debounce(fn) {
let t = null
return function () {
//如果定时器存在就清除掉
if (t) {
clearTimeout(t)
}
//不然就创建新的定时器
t = setTimeout(function() {
fn()
}, 1000)
}
}
</script>
看完代码你可能会有如下疑惑:
1. 怎么这个debounce函数里面不能直接写执行内容吗?非要return一个函数?
解:因为你已经把changeColor函数当成参数传给debounce函数了(看下面的代码变化),你要在btn上绑定debounce事件就要在debounce里面将改造好的changeColor事件写成一个回调函数。
// btn.addEventListener('click', changeColor, false);
btn.addEventListener('click', debounce(changeColor), false);
2. 这个 let t = null; 岂不是会每次都将 t 置为 null ??这可咋实现呢?
解:你以为 btn 绑定的是 debounce,所以你认为每次触发都会执行 t = null。
漏! btn 绑定的是debounce(changeColor)!这个括号不可忽视,实际上每次触发click事件执行的是它的回调,也就是 return 里面的内容,let t = null 只会在初始化的时候执行一次。
虽然目前已经实现了防抖的效果,但是这么写的话,你会发现changeColor函数是拿不到事件对象的,也就是说它拿不到本该属于它的 event 。
要想让它拿回本该属于它的 event ,你可以这么做:
1.基于当前 event 已经在 debounce 上了,你可以将 e 当参数传递给 debounce 里回调的函数
2.再把e传给回调函数中定时器里的 fn()
function debounce(fn) {
let t = null
//往这里传e
return function (e) {
if (t) {
clearTimeout(t)
}
t = setTimeout(function() {
//再带给fn
fn(e)
}, 1000)
}
}
届时,你就会发现 changeColor 它拿到了事件对象!
如果你问,非要拿这个e干啥呢??
那么我只能说,拿到了e可以对e做一些操作(像是e.target可以更改等等),如果你不对e做什么不传给它也没关系,只是这么写可以离一个完美的防抖函数更近而已。
但是你不能保证只有一个参数需要传给 changeColor ,所以在传参的时候只写一个 e 没办法实现多个参数的传递,那么为了更完美一点,我们接着来改改。
说到多个参数的传递你大概会想到实参列表 arguments 。没错!就是它!
如果你不了解 arguments,请前往:学arguments去喽
arguments
对象是所有(非箭头)函数中都可用的局部变量。你可以使用arguments
对象在函数中引用函数的参数。此对象包含传递给函数的每个参数,第一个参数在索引0处。
首先,我们试着用 arguments[0] 去代替刚刚传递的 e ,你会发现:
那个return里的 arguments[0] 根本传不到定时器里,那就更别提传到 changeColor 了。
因为每个函数里的 arguments 都是自己函数内部的,定时器里的函数没有 arguments 所以 undefined。
要解决这个问题的话,你可以使用赋值的方式把 arguments 存下来,还可以使用箭头函数。
接下来采用箭头函数的方式来解决:
因为箭头函数内部没有 arguments 对象,它会往外找,这样就可以得到 return 里的 arguments。
function debounce(fn) {
let t = null
return function () {
console.log('我是回调的arguments',arguments[0]);
if (t) {
clearTimeout(t)
}
//这里用箭头函数
t = setTimeout(() => {
fn(arguments[0])
console.log('我是定时器里的arguments', arguments[0])
// console.log(this)
}, 1000)
}
}
apply 方法 将整个 arguments 作为参数传递过去,如下:
function debounce(fn) {
let t = null
return function () {
if (t) {
clearTimeout(t)
}
t = setTimeout(() => {
fn.apply(this, arguments)
}, 1000)
}
}
其中,apply方法的第一个参数还可以将 changeColor 的 this 由 Window 转为 btn,属于是一个一举两得的大动作了。 (因为箭头函数会往外找this继承,所以拿到了return里的this再传给changeColor)
如果你不了解apply,请前往:学apply去喽
写到这里,一个 延迟debounce
什么是延迟debounce??
顾名思义,在延迟结束那一刻才触发回调。
如果你觉得每次按完按钮还要等等才能改颜色真是太烦了,估计没等到改颜色你就关闭网页了。
前缘debounce
将代码再改一改你就可以得到 前缘debounce 啦!
function debounce(fn) {
let t = null
return function () {
// 用firstClick来记录每一次定时器开始的第一次按下的动作
var firstClick = !t
if(t) {
clearTimeout(t)
}
if(firstClick) {
fn.apply(this, arguments)
}
//等待1秒后将 t 置为 null,在这1秒内如果再点击事件就会去到if(t)的执行
t = setTimeout(() => {
t = null
}, 1000)
}
}
这个前缘debounce将会实现每次连续点击后先响应一次事件,再去等1秒。
以上是我个人对防抖的理解与实现,如有不当欢迎指正!
码字不易,走过路过,拜托点个赞再走吧 T T