前言
函数的防抖与节流在项目中有很多的使用场景,本文参考了冴羽的两篇文章。
函数防抖
概念
指触发事件后,在 n 秒内函数只能执行一次,如果触发事件后在 n 秒内又触发了事件,则会重新计算函数延执行时间。
举个栗子,坐电梯的时候,如果电梯检测到有人进来(触发事件),就会多等待 10 秒,此时如果又有人进来(10秒之内重复触发事件),那么电梯就会再多等待 10 秒。在上述例子中,电梯在检测到有人进入 10 秒钟之后,才会关闭电梯门开始运行,因此“函数防抖”的关键在于,在一个事件发生一定时间之后,才执行特定动作。
场景代码 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>Document</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>
<script>
var count = 1;
var container = document.getElementById('container');
function getUserAction() {
container.innerHTML = count++;
};
container.onmousemove = debounce(getUserAction, 1000);
function debounce () {
...
}
</script>
</body>
</html>
基础版
根据上面的表述,实现第一版的代码:
function debounce(func, wait) {
var timeout;
return function () {
var context = this;
var args = arguments;
clearTimeout(timeout)
timeout = setTimeout(function(){
func.apply(context, args)
}, wait);
}
}
第一版的代码没什么难点。一个事件在 n 秒内重复触发,会一直清除定时器,等停止触发 n 秒后定时器的方法才会执行。同时也解决了定时器中方法的 this 指向和 JavaScript 在事件处理函数中会提供事件对象 event 参数问题。
立刻执行
第一版代码事件需要在 n 秒后执行。一个新的需求是希望立刻执行函数,然后等到停止触发 n 秒后,再重新触发执行。
增加 immediate 参数判断是否是立刻执行。
function debounce(func, wait, immediate) {
var timeout;
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) func.apply(this, args)
} else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
}
}
返回值
getUserAction 函数可能是有返回值的,所以这里也需要返回函数的结果。但当 immediate 为 false 的时候,因为使用 setTimeout,在最后 return 的时候值会一直是 undefined。所以只在 immediate 为 true 的时候返回函数的执行结果。
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(this, args)
} else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
return result
}
}
取消
实现让用户执行 cancel 方法来取消防抖,当用户再次去触发时又可以立刻执行。
function debounce(func, wait, immediate) {
var timeout, result;
var debounced = 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(this, args)
} else {
timeout = setTimeout(function () {
func.apply(context, args)
}, wait);
}
return result
}
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced
}
函数节流
概念:限制一个函数在一定时间内只能执行一次。
举个栗子,坐火车或地铁,过安检的时候,在一定时间(例如10秒)内,只允许一个乘客通过安检入口,以配合安检人员完成安检工作。上例中,每10秒内,仅允许一位乘客通过。分析可知“函数节流”的要点在于,在一定时间之内,限制一个动作只执行一次 。
// func (Function): 要节流的函数
// [wait=0] (number): 需要节流的毫秒
// [options={}] (Object): 选项对象
// [options.leading=true] (boolean): 指定调用在节流开始前
// [options.trailing=true] (boolean): 指定调用在节流结束后
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function () {
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () {
var now = Date.now();
if (!previous && options.leading === false) 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 && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}