工具函数系列-节流throttle
节流的原理是:固定时间间隔执行函数
应用场景:resize
、scroll
function throttle(func, wait) {
var timeout, context, args;
var previous = 0;
var later = function() {
previous = +new Date();
timeout = null;
func.apply(context, args)
};
var throttled = function() {
var now = +new Date();
//下次触发 func 剩余的时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果没有剩余的时间了或者你改了系统时间
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}
function throttle2(func, wait) {
var timeout = null;
var previous = 0;
return function() {
var args = arguments;
var context = this;
var now = +new Date();
var remainning = wait - (now - previous); // 等待时间减去两次触发事件的时间差
timeout && clearTimeout(timeout)
if (remainning <= 0) { // 两次触发时间差大于等待时间,立马执行
func.apply(context, args)
previous = now // 更新时间
} else { // 小于等待时间,等待一段时间后执行
timeout = setTimeout(function () {
func.apply(context, args)
provious = +new Date()
}, remainning) // 修改设置timeout时间
}
}
}
我们可以看到:鼠标移入,事件立刻执行,晃了 3s,事件再一次执行,当数字变成 3 的时候,也就是 6s 后,我们立刻移出鼠标,停止触发事件,9s 的时候,依然会再执行一次事件。
优化
取消第一次执行或者取消最后一次执行
那我们设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,我们约定:
leading:false 表示禁用第一次执行
trailing: false 表示禁用停止触发的回调leading和trailing不能同时设置,如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了
function throttle(func, wait, options) {
var timeout, context, args;
var previous = 0;
if (!options) options = {}; // 设置默认值 可以使用es6设置默认值的方式
var later = function() {
previous = options.leading === false ? 0 : +new Date();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = +new Date();
if (!previous && options.leading === false) previous = now;
var remainning = wait - (now - previous);
context = this;
args = arguments;
if (remainning <= 0 || remainning > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remainning)
}
}
return throttled
}
取消
// 接上一版
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = null;
}
// ...
参考文章:https://github.com/mqyqingfeng/Blog/issues/26
测试
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
<title>debounce</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() {
let num = count++
container.innerHTML = num;
console.log(Date.now(), num)
};
container.onmousemove = throttle(getUserAction, 1000);
function throttle(func, wait) {
var timeout, context, args, result;
var previous = 0;
var later = function() {
previous = +new Date();
timeout = null;
func.apply(context, args)
};
var throttled = function() {
var now = +new Date();
//下次触发 func 剩余的时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果没有剩余的时间了或者你改了系统时间
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}
</script>
</body>
</html>