防抖和节流的作用都是防止函数多次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于设置的时间,防抖的情况下只会调用一次,而节流的情况会每隔一定时间调用一次函数。
1. 防抖
⑴ 函数防抖(debounce
):n
秒内函数只会执行一次,如果n
秒内高频事件再次被触发,则重新计算时间。如下例,对于高频操作Mousemove
,没有限制count
函数执行频率时,数字在不停的加一:
<body>
<div id="content" style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
<script>
let num = 1;
let content = document.getElementById('content');
function count() {
content.innerHTML = num++;
}
content.onmousemove = count;
</script>
</body>
而防抖的场景即指的是持续触发mousemove
事件时,并不执行count
函数,当1000毫秒内没有触发Mousemove
事件时,才会延时触发count
事件。
防抖封装:
<!DOCTYPE html>
<html lang="en">
<head>
<title>practise</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<meta name="viewport" id="WebViewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<style>
</style>
</head>
<body>
<div id="content" style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
<script>
let num = 1;
let content = document.getElementById('content');
function count() {
content.innerHTML = num++;
}
content.addEventListener("mousemove", debounce(count, 1000));
function debounce(func, wait, immediate=true) {
let timeout, context, args;
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空定时器
timeout = null;
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {
func.apply(context, args);
context = args = null;
}
}, wait);
let debounced = function (...params) {
if (!timeout) {
timeout = later();
if (immediate) {
//立即执行
func.apply(this, params);
} else {
//闭包
context = this;
args = params;
}
} else {
clearTimeout(timeout);
timeout = later();
}
};
debounced.cancel = function () {
clearTimeout(timeout);
timeout = null;
};
return debounced;
}
</script>
</body>
</html>
⑵ 防抖的应用场景:
- 每次
resize
/scroll
触发统计事件 - 文本输入的验证(连续输入文字后发送
AJAX
请求进行验证,验证一次就好)
2. 节流
⑴ 函数节流:高频事件在规定时间内只会执行一次,执行一次后,只有大于设定的执行周期后才会执行第二次。如下图,持续触发Mousemove
事件时,并不立即执行count
函数,每隔1000毫秒才会执行一次count
函数。
节流封装:
<!DOCTYPE html>
<html lang="en">
<head>
<title>practise</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
<div id="content" style="height:150px;line-height:150px;text-align:center; color: #fff;background-color:#ccc;font-size:80px;"></div>
<script>
let num = 1;
let content = document.getElementById('content');
function count() {
content.innerHTML = num++;
}
content.addEventListener("mousemove", throttle(count, 1000));
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() || new Date().getTime();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () {
var now = Date.now() || new Date().getTime();
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) {
// 判断是否设置了定时器和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}
</script>
</body>
</html>
⑵ 函数节流的应用场景有:
- DOM 元素的拖拽功能实现(mousemove)
- 射击游戏的 mousedown/keydown 事件(单位时间只能发射一颗子弹)
- 计算鼠标移动的距离(mousemove)
- Canvas 模拟画板功能(mousemove)
- 搜索联想(keyup)
- 监听滚动事件判断是否到页面底部自动加载更多:给 scroll 加了 debounce 后,只有用户停止滚动后,才会判断是否到了页面底部;如果是 throttle 的话,只要页面滚动就会间隔一段时间判断一次
参考自:
李赵同学猛猛哒~ 前端麻辣烫 https://mp.weixin.qq.com/s/Vkshf-nEDwo2ODUJhxgzVA
刘小夕 https://juejin.im/post/5cab0c45f265da2513734390#heading-22