Underscore.js源码地址:https://unpkg.com/underscore@1.13.7/underscore.js
1.节流和防抖对比
- 相同点:都是前端性能优化的一种手段,能够降低用户操作触发频次,从而提高效能
- 不同点:实现方式有所不同
- 节流:控制连续操作过程中,一段时间内只能执行一次
- 防抖:控制频繁操作,中间间隔时间超过 delay 等待时间,才执行一次。即连续操作N次,只执行最后一次
具体效果可以看下面视频演示
2.节流的实现
underscore.js
// getTime() 方法可返回距 1970 年 1 月 1 日之间的毫秒数
var now = Date.now || function() {
return new Date().getTime();
};
/**
* 节流方法定义
* @param {*} func 执行函数
* @param {*} wait 等待时间
* @param {*} options 配置参数
* @returns
* 创建并返回一个像节流阀一样的函数,当重复调用函数的时候,至少每隔 wait 毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。
默认情况下,throttle 将在你调用的第一时间尽快执行这个 func函数,并且,如果你在 wait 周期内调用任意次数的函数,都将尽快的被覆盖。
如果你想禁用第一次首先执行的话,传递 {leading: false},
还有如果你想禁用最后一次执行的话,传递 {trailing: false}。
*/
function throttle(func, wait, options={}) {
// result 用于接收 func 函数的返回值
var timeout, context, args, result;
// previous 上一次点击时刻的毫秒数
var previous = 0;
if (!options) options = {};
var later = function () {
// leading 为 false,禁用第一次首先执行
previous = options.leading === false ? 0 : now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () { //输入事件触发函数
// 获取当前时刻的毫秒数
var _now = now();
if (!previous && options.leading === false) {
previous = _now;
}
// 剩下需要等待的毫秒数 = 用户设置的触发间隔毫秒数 - 实际距离上一次点击间隔的毫秒数
var remaining = wait - (_now - previous);
// 上下文this
context = this;
// 函数内置对象:所有参数
args = arguments;
// 等待间隔数已经超过等待时间了,可以直接执行操作
if (remaining <= 0 || remaining > wait) {
if (timeout) {
// 清除定时器
clearTimeout(timeout);
timeout = null;
}
// 保存当前点击时刻毫秒数,作为 下一次点击时 的 上一次点击时间
previous = _now;
// 执行func
result = func.apply(context, args);
if (!timeout) {
context = args = null;
}
} else if (!timeout && options.trailing !== false) { //还在等待时间内并且不禁用最后一次执行,用定时器延迟等待渡过剩余时间,再执行;
// 注意:if(!timeout)是必要的,要判断当前有 timeout 就不重复创建,在上面超过等待时间时,才去清除定时器,避免重复创建
timeout = setTimeout(later, remaining); // remaining:剩余等待毫秒数
}
return result;
};
// 取消预定的 throttle
throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
}
3.防抖的实现
源码写法:
// 当返回函数的一系列调用结束时,将触发参数函数。序列的结束由 wait 参数定义。如果传入 immediate,则参数函数将在序列的开始而不是结束时触发。
function debounce(func, wait, immediate) {
var timeout, previous, args, result, context;
var later = function () {
// 距上一次点击的时间
var passed = now() - previous;
// 如果没到 wait等待时间,就接着等待
if (wait > passed) {
// 递归调用,等待时间 = 总共需等待时间wait - 已等待时间passed
timeout = setTimeout(later, wait - passed);
} else { // 够等待时间了,调用 func
timeout = null;
if (!immediate) result = func.apply(context, args);
// 这个检查是必要的,因为 func 可以递归地调用 debounced
if (!timeout) args = context = null;
}
};
// 源码这里调用了 restArguments方法获取剩余参数,这里简化了
var debounced = function (..._args) {
context = this;
args = _args;
previous = now();
if (!timeout) {
timeout = setTimeout(later, wait);
// immediate为true,立即执行
if (immediate) result = func.apply(context, args);
}
return result;
};
debounced.cancel = function () {
clearTimeout(timeout);
timeout = args = context = null;
};
return debounced;
}
整体简化版本:更好理解
// 防抖:wait等待时间内如果再次操作,就取消上次计时,重新timer计时,直到停止操作等待wait时间后,再执行
function debounce(fn, wait, immediate) {
let context, args, timer;
let debounced = function () {
context = this;
args = arguments;
if (timer) {
clearTimeout(timer);
timer = null;
}
// immediate为true,且立即执行
if (immediate && !timer) {
fn.apply(context, args)
}
timer = setTimeout(() => {
fn.apply(context, args)
clearTimeout(timer);
timer = null;
}, wait)
}
// 取消预订的 debounce
debounced.cancel = function () {
clearTimeout(timer);
context = args = timer = null;
}
return debounced
}
节流防抖函数调用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
节流:<input style="width: 90%;" id="input" type="text" oninput="throttled()">
</div>
<div>
防抖:<input style="width: 90%;" id="input2" type="text" oninput="debounced()">
</div>
<script src="./underscore.js"></script>
<script>
var inputDom = document.getElementById("input");
var inputDom2 = document.getElementById("input2")
function func() {
console.log(inputDom.value);
}
function func2() {
console.log(inputDom2.value);
}
var throttled = throttle(func, 1000);
var debounced = debounce(func2, 1000);
</script>
</body>
</html>
4.效果演示
节流防抖函数效果演示
5.应用场景
- 节流:鼠标拖拽或滚动加载事件,降低调用执行函数的频率;
- 防抖:input输入框,边输入边请求后台,等输入中断一定时间再请求,连续输入就不重复请求。