throttle和debounce
这两个都是处理密集调用的函数,看高级程序设计 614 页的时候,发现写的有问题,书里将 throttle 和 debounce 搞混了。于是重新整理了一下。
意义解释
throttle 这个是在高频调用时,保证多少毫秒内只会被执行一次,还是会不断地触发,但是限制了频率。
特别适合的场景:比如在 resize 触发的时候,页面会被不停地重绘,如果在 resize 里面还加上了 DOM 操作的话,对于浏览器的压力很大。设置了 throttle 之后,就可以设置成 50ms 触发一次重绘。
debounce 是在多少毫秒内不再被触发的时候,就会执行一次,如果不停的调用的话,他实际上最后其实只是执行一次。比如在线编辑的自动存储,比较适合这种处理的逻辑。
自己实现 throttle
自己根据特性简单的实现了一下
function throttle(fn, timeout, context) {
//如果上次没调用过,直接调用
if (!fn.lastExec) {
fn.lastExec = Date.now();
fn.call(context);
} else {
//如果调用过 --- 如果这次的调用时间超过了设置的设定好的timeout
var remaining = Date.now() - fn.lastExec;
if (remaining > timeout) {
//新调用
fn.lastExec = Date.now();
fn.call(context);
}
}
}
var fn = function() {
console.log(Date.now());
};
for (var i = 1; i <= 10; i++) {
setTimeout(function() {
throttle(fn, 2000);
}, 1000 * i);
}
function debounce(fn, timeout, context) {
//如果上次调用过还没执行,就清除掉,重设定时
clearTimeout(fn.dId);
fn.dId = setTimeout(function() {
fn.call(context);
}, timeout);
}
我写的 throttle 的函数的缺陷:
- 我的节流会根据调用的频率产生误差,频率越小,我的误差会越大,因为我只是阻止了时间内的调用,并没有为了他再设置一个定时器
- 我把数据都直接挂在了 fn 上,太 Low 了,应该用一个闭包,把数据存在内存中,并且外部也没有访问的可能
underscore 的实现 throttle
顺便去看下别人的模块是怎么写的
_.throttle = function(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
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);
context = this;
args = arguments;
//如果剩余时间小于0,清空timeout,立即调用
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并且不禁止最后一次调用,那就设置定时重新运行
timeout = setTimeout(later, remaining);
}
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};
var fn = _.throttle(function() {
console.log(Date.now());
}, 2000);
for (var i = 1; i < 10; i++) {
setTimeout(fn, 1000 * i);
}
underscore 的实现解决了我的调用的误差问题,如果时间到了,直接执行;时间不到,就设置一个剩余时间的定时器来执行。很有意思的实现。
自己实现 debounce
function debounce(fn, timeout, context) {
//如果上次调用过还没执行,就清除掉,重设定时
clearTimeout(fn.dId);
fn.dId = setTimeout(function() {
fn.call(context);
}, timeout);
}
var fn = function() {
console.log(Date.now());
};
for (var i = 1; i <= 5; i++) {
setTimeout(function() {
debounce(fn, 2000, null);
}, 1000 * i);
}
debounce 的实现比较简单,应该也不会错。除了没用闭包,有些 low 之外。
underscore 的实现 debounce
_.debounce = function(func, wait, immediate) {
var timeout, result;
var later = function(context, args) {
timeout = null;
if (args) result = func.apply(context, args);
};
var debounced = restArgs(function(args) {
var callNow = immediate && !timeout;
if (timeout) clearTimeout(timeout);
if (callNow) {
//如果立即调用,就设置一个定时,并且调用下方法。
timeout = setTimeout(later, wait);
result = func.apply(this, args);
} else if (!immediate) {
//否则,更新定时的时间
timeout = _.delay(later, wait, this, args);
}
return result;
});
debounced.cancel = function() {
clearTimeout(timeout);
timeout = null;
};
return debounced;
};
- 他的实现主要是加上了 immediate 的支持,就是在第一次调用的时候直接执行,并且在接下来的时间内不会再执行。用于防止那种不小心提交按钮点击了两次的情况是很管用的。
- 而且他的实现都提供了 cancel 方法来清除定时,防止出问题,蛮好的。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 981909093@qq.com
文章标题:throttle和debounce
文章字数:1k
本文作者:泽鹿
发布时间:2019-08-28, 16:45:23
最后更新:2019-08-28, 16:45:23
原始链接:http://panyifei.github.io/2019/08/28/技术/前端技术/throttle和debounce/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。