函数节流与防抖在操作dom的时候很实用。比如用户在点击提交按钮时,防止用户重复点击导致多次提交一样的数据,为了限制用户重复点击,我们需要采取措施来防止重复提交数据,这就可以用到我们所说的函数防抖,在一定时间类触发同样的事件时,只执行最后一次。最近正好在看underscore.js
的源码,看到了节流和防撞的实现,今天抽时间来整理下函数的节流与防抖在实际开发中的应用场景。
1.节流
节流是指函数按照固定的时间来执行一次,比如调用鼠标事件的mousemove
时,如果没做任何处理,当鼠标移动的时候,会一直触发mousemove
的回调函数,假设使用函数节流,将固定时间设置为200毫秒一次,在这200毫秒内,无论鼠标怎么移动,都不会触发`mousemove``的回调函数,这就是我们所说的节流。
1.1应用场景
主要是应用在一些高频事件,比如mousemove
,keyup
等。
1.2实现原理
节流的实现原理主要是通过定时器来完成,设定一个时间后,判断定时器是否为空,如果定时器为空就立即执行,定时器不为空时,在定时器设置的时间内不执行回调函数,达到设置的时间后才执行回调函数,具体的实现代码如下所示,代码使用的是underscore.js
中节流的代码。
/*
* 节流
* */
this.throttle = function (func,wait) {
var timeout, context, args, result;
var previous = 0;
var self = this;
var later = function() {
previous = self.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = self.now();
if (!previous) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}
2.防抖
当持续触发事件时,在一定时间内没有再次触发时才会执行一次,如果在指定时间内持续触发,每次触发的时候将重新计时。比如给按钮添加点击事件时,用户快速的连续点击按钮,只执行规定时间内最后执行的那一次。
2.1应用场景
主要是应用在一些持续触发事件,比如快速点击,scroll
,resize
等事件。
2.2实现原理
防抖的实现原理还是借助于定时器,当持续触发函数时,每次将定时器清空重置,重新生成一个定时器,然后再指定的时间后执行回调函数,具体的实现代码如下所示,代码使用的是underscore.js
中节流的代码。
/*
* 去抖
* */
this.debounce = function (func,wait,immediate) {
var timeout, args, context, timestamp, result,self = this;
var later = function() {
var last = self.now() - timestamp;
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout)
context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
timestamp = self.now();
var callNow = immediate && !timeout;
if (!timeout)
timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
}
实例
针对mousemove
事件和click
事件写了一个简单的demo
,代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>节流去抖</title>
<style>
#red-layout{
width: 100px;
height: 100px;
position: absolute;
background-color: red;
}
</style>
</head>
<body onload="init()">
<div id="red-layout"></div>
</body>
<script>
function init() {
var util = new Util();
var layout = document.getElementById('red-layout');
layout.addEventListener("mousemove", util.throttle(function (event) {
console.log('mousemove');
},500));
layout.addEventListener('click',util.debounce(function () {
console.log('click');
},2000))
}
function Util() {
/*
* 节流
* */
this.throttle = function (func,wait) {
var timeout, context, args, result;
var previous = 0;
var self = this;
var later = function() {
previous = self.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = self.now();
if (!previous) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}
/*
* 去抖
* */
this.debounce = function (func,wait,immediate) {
var timeout, args, context, timestamp, result,self = this;
var later = function() {
var last = self.now() - timestamp;
if (last < wait && last >= 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
if (!immediate) {
result = func.apply(context, args);
if (!timeout)
context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
timestamp = self.now();
var callNow = immediate && !timeout;
if (!timeout)
timeout = setTimeout(later, wait);
if (callNow) {
result = func.apply(context, args);
context = args = null;
}
return result;
};
}
/*
* 获取当前时间
* */
this.now = Date.now || function() {
return new Date().getTime();
}
}
</script>
</html>