为什么要使用防抖、节流?
开发中经常遇到一些需要频繁触发的事件,例如window对象的视口大小改变、滚动(resize,scroll)、鼠标移动事件(mousedown,mousemove等),表单标签的键盘按下弹起事件(keydown,keyup)等等;
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text" name="" id="" value="" />
</body>
<script type="text/javascript">
document.querySelector("input").addEventListener("keyup", e => {
console.log(e.target.value)
})
</script>
</html>
又比如,我们在事件触发后需要请求数据,这样多频率的请求数据显然是不可取的,为了规避类似事件的频繁触发,我们可以使用防抖,节流。
防抖
原理:
事件触发n秒后再去执行回调函数,若在n秒内事件再次触发,则时间重新计时,实质是闭包。结果就是将频繁触发的事件合并为一次,且在最后执行。
代码实现:
初步实现:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text" name="" id="" value="" />
</body>
<script type="text/javascript">
function debounce(callback,wait){
let timer;
console.log(this)
return () => {
clearTimeout(timer);
timer = setTimeout(() => {
callback()
},wait)
}
}
function callback(){
console.log('回调函数执行了');
}
document.querySelector("input").addEventListener("keyup",debounce(callback,1000))
</script>
</html>
但是开发中几乎都是需要从目标节点上获取信息执行回调,比如输入用户名查询用户,需要获取输入的用户名发请求查询。可以用apply(apply详解)改变回调函数的this指向,从而获取节点信息,并且通过实参集合(arguments)把目标节点的事件对象也传入到回调函数中,保证回调函数中也可拿到事件对象操作。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text" name="" id="" value="" />
</body>
<script type="text/javascript">
function debounce(callback,wait){
let timer;
return function(){
clearTimeout(timer);
timer = setTimeout(() => {
callback.apply(this,arguments)
},wait)
}
}
function callback(e){
console.log('回调函数执行了');
console.log(e);
console.log(this);
}
document.querySelector("input").addEventListener("keyup",debounce(callback,1000))
</script>
</html>
这样就完美了吗?非也非也。倘若有些需求是在事件触发后回调函数立即执行,比如滚动加载,当触底后则立即执行回调。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<input type="text" name="" id="" value="" />
</body>
<script type="text/javascript">
function debounce(callback,wait,immediate){
let timer;
return function(){
clearTimeout(timer);
if(immediate){
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
},wait)
//立即执行
if(callNow) callback.apply(this,arguments);
}else{
timer = setTimeout(() => {
callback.apply(this,arguments)
},wait)
}
}
}
function callback(e){
console.log('回调函数执行了');
console.log(e);
console.log(this);
}
document.querySelector("input").addEventListener("keyup",debounce(callback,2000,true))
</script>
</html>
最后就是添加取消防抖功能:
扫描二维码关注公众号,回复:
12804017 查看本文章
function debounce(callback,wait,immediate){
let timer;
return function(){
clearTimeout(timer);
if(immediate){
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
},wait)
//立即执行
if(callNow) result = callback.apply(this,arguments);
}else{
timer = setTimeout(() => {
callback.apply(this,arguments)
},wait)
}
}
debounce.cancel = () => {
clearTimeout(timer);
timer = null
}
}
防抖就到这了,接下来说说节流吧。
节流
原理:
在频繁触发事件中,间隔n秒执行一次回调函数。
代码实现:
节流有两种实现方式,用时间差和定时器。
// 时间差实现
function throttle(callback, wait) {
let oldtime = 0;
return function() {
let now = new Date().getTime();
if (now - oldtime > wait) {
callback();
oldtime = now
}
}
}
// 定时器实现
function throttleTimeOut(callback, wait) {
let timeOut;
return function() {
if(!timeOut){
timeOut = setTimeout(() => {
timeOut = null;
callback.apply(this,arguments);
},wait)
}
}
}
function fn(e) {
console.log(this.value)
console.log(e);
}
document.querySelector("input").addEventListener("keyup", throttleTimeOut(fn, 1000))
两种方法实现的节流,用时间差实现的节流是立即执行最后一次不执行(顾头不顾尾),定时器实现的则是不立即执行,最后一次执行(顾尾不顾头);两者结合就可以得到顾头顾尾的节流函数:
function throttle(callback, wait) {
let timeOut;
let oldtime = 0;
return function() {
let now = new Date().getTime();
if (now - oldtime > wait) {
if(timeOut){
clearTimeout(timeOut);
timeOut = null;
}
callback.apply(this,arguments);
oldtime = now
}
if(!timeOut){
timeOut = setTimeout(() => {
oldtime = new Date().getTime()
timeOut = null;
callback.apply(this,arguments);
},wait)
}
}
}
参考博客:js 防抖实战讲解