什么是节流
在函数调用过程中,避免过于频繁的调用,而是间隔某一时间后调用一次的方法,叫做节流。
节流做什么
节流可以有效避免短时间内大量调用某一方法或数据,保证页面的稳定性和数据的准确性。
一个小的例子
使用 underscore 的节流功能来测试一下效果。
在页面中直接导入 cdn 即可。
https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js
未节流时的样子
将下面内容粘贴到一个 HTML 的 body 标签中。
<div
id="container"
style="width:100%;height:200px;line-height:200px;text-align:center;color:#fff;background-color:#444;font-size:30px;"
></div>
<script>
let count = 0;
let container = document.querySelector("#container");
// 此处为高频调用函数
function doSomething() {
container.innerHTML = count++;
}
container.onmousemove = doSomething;
</script>
这段代码会生成一个灰色框,只要鼠标在其内部移动,就会调用 doSomething
函数,导致数字不断增长。
使用节流
同样地,将下面内容粘贴到一个新的 HTML 的 body 标签中。
<div
id="container"
style="width:100%;height:200px;line-height:200px;text-align:center;color:#fff;background-color:#444;font-size:30px;"
></div>
<script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore.js"></script>
<script>
let count = 0;
let container = document.querySelector("#container");
// 此处为高频调用函数
function doSomething() {
container.innerHTML = count++;
}
container.onmousemove = _.throttle(doSomething, 300);
</script>
这里导入了
underscore
库,这个库会导出一个_
的对象,它包含一个throttle
方法,该方法的作用就是节流。第一个参数是函数原型,第二个参数是响应时间,这里我们设置 300ms 后响应。另外还可以设置是否第一次首先执行和最后一次是否执行,传入:
{leading: false}
或{trailing: false}
。当然,你也可以同时传入,但是需要注意,如果同时传入false
的话,会出现 bug,该 bug 会导致下次启动时不响应{leading: false}
,而是会立即执行第一次,这一点需要注意。
这次运行后,可以发现当鼠标移动时,不再一味地增加,而是每隔一段时间后(300ms)才会响应一次。
手动实现节流函数
利用时间戳方式
这样的方式会触发第一次,而不会触发最后一次。
function throttle(func, wait) {
let context, args;
let old = 0;
return function () {
context = this;
args = arguments;
let now = Date.now();
if (now - old > wait) {
// 立即执行
func.apply(context, args);
old = now;
}
};
}
利用定时器方式
这样的方式会触发最后一次,而不会触发第一次。
function throttle(func, wait) {
let context, args, timeout;
return function () {
context = this;
args = arguments;
// 设置一个定时器,只有为空时才会触发,每次执行后都会重新设定一个定时器。
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args);
}, wait);
}
};
}
合并为最终的完整本版
利用上面的两种实现方式,可以得到一个比较完整的版本。
function throttle(func, wait, options) {
let context, args, timeout, result;
let previous = 0;
if (!options) options = {};
let later = function () {
// 这里控制再次出发时,第一次是否执行,当为双false时,也是这里会出现的问题。
previous = options.leading === false ? 0 : Date.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function () {
context = this;
args = arguments;
let now = Date.now();
// 第一次不执行,调整previous的值即可。
if (!previous && options.leading === false) previous = now;
if (now - previous > 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, wait);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
timeout = null;
timeout = context = args = null;
}
return throttled;
}
文章评论