underscore 函数去抖的实现 #21
Comments
基础版最重要的是, 有side effect吧. |
@joesonw 请教下 side effect 具体是? |
改变了输入值, 给function多加了属性. |
发现个问题,这个 clearTimeout() 直接让 setTimeout() 中的函数不执行,而不是调用 clearTimeout 之后立即执行里面的函数。也就是说,setTimeout() 的回调会在最后一次执行 debounce() 后起作用。这样就保证了只执行一次,就是节流啊。。。 |
@jsspace 我的理解是 「节流」(throttle)是控制函数执行的频率,而不是只执行一次(debounce) |
哦哦,我弄混了 |
throttle 和 debounce 的应用场景应该是分的很清楚的
|
@riskers 不错,给 throttle 应用加了这个 case |
@joesonw ,你说的 side effect 是可以消除的。其实不必要非得给 function 添加这个属性,只要是一个在 debounce 函数外部的变量就可以。高程三里的这个写法其实是可改成下面这个样子的: var timer = null;
function debounce(method, context) {
clearTimeout(timer);
timer = setTimeout(function() {
method.call(context);
}, 1000);
}
function print() {
console.log('hello world');
}
window.onscroll = function() {
debounce(print);
}; 为了避免对全局的污染,其实最好的方式是将 timer 放入函数中,成为一个局部变量,所以上面的写法可以改写成下面的方式: function debounce(method, context) {
var timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(function() {
method.call(context);
}, 1000);
}
}
function print() {
console.log('hello world');
}
window.onscroll = debounce(print); 从这个意义上讲,闭包其实就是用来将两个内容隔离用的,将 timer 放入函数中,那么就需要将原来的语句放入函数中,使其与 timer 隔离,最近返回这个函数。结果就会和原来的效果是一样的。 |
@oakland |
debounce 有种 hold 住的感觉,一个动作不停地被触发,但是又不停地被终止,两次触发之间的时间长于给定的时间段才会真正触发这个时间。 function debounce(method, context) {
var timer = null;
var n = 0;
return function() {
clearTimeout(timer);
timer = setTimeout(function() {
method.call(context);
}, 1000);
console.log(n++);
}
}
function print() {
console.log('hello world');
}
window.onscroll = debounce(print); |
我感觉这里是这样的:func作为用户传入的任意函数,有可能会反过来调用debounce返回的新函数,比如 var func, de, i = 0;
func = function() {
i++;
if (i < 10) {
console.log(i);
de();
// setTimeout(de, 10);
}
};
de = _.debounce(func, 40);
de(); 这个会输出1到9,改改条件应该就能出现 de -> func -> de这种嵌套调用了。 更新1:我才意识到我也把debounce当成节流了,抱歉。
var f, d, tick = 0;
f = function() {
console.log('tick:', ++tick, [].slice.call(arguments, 0));
if (tick === 1) {
return d(1, 2) || 'tick-1 but d(1,2) returns empty';
}
return 'tick-' + tick;
};
d = _.debounce(f, 100, true);
var ret1 = d('ni hao');
console.log('first result', ret1);
|
话说您用的understore 1.8.3 和 现在的jashkenas/underscore:master (https://github.com/jashkenas/underscore/blob/97cfcbcbbcedf544a13127dcca3e0ddad94ff830/underscore.js) 差了很多啊,_.debounce 完全被重写了。 我有个疑问是,master上的debounce已经在每次进入时就clearTimeout了,和您的“性能优化”的解释不一样,请问这两个方案的真正差别是什么?是应用场景导致的取舍吗?
|
@joesonw 基础方法确实有隐患,如果传入的method是一个匿名函数,绑定到匿名函数的timer将不会被清理掉 |
@gitwd 请问为什么匿名函数timer不会被清理?区别在哪里呢? |
我认为你这儿说的有问题,setTimeout是不精准延时,debounce里面补充判断如果last在[0,wait)区间,则继续setTimeout一个wait-last的时间再执行函数,保证函数执行程序一定在延时了wait之后执行。 |
@hanzichi 韩老师你好!我想请教一下一个问题。 在您所阅读的underscore源码中(1.8.3),假设有如下代码:
我的理解是: a只有第一次被调用时才会进入later函数,但每次调用a都会更新时间戳Timestamp,而later内部会计算时间差,时间差不足时,递归调用later计算时间差,一旦时间差足够就触发传入的异步函数,最终执行的还是只有最后一个a函数。不知道正不正确? 我现在阅读的源码是最新版的,其中的_.debounce函数已经完全改进了,不再依赖于计算时间差,而是利用了JavaScript的异步机制: 假设有同样一段代码在最新版underscore中执行:
我是否可以这样理解: JavaScript优先执行完执行队列中的同步代码(以上所有代码)之后,再去执行事件队列中的异步代码。上方程序在执行所有同步代码时,每次a函数被调用,都会clearTimeout取消事件队列中的异步任务,导致前文a函数设置的异步任务被取消,直到最后一个a函数被执行时,才会开始计时,最终执行的也会是最后一个a函数。 两者相比较而言,后者使用变量更少,递归调用更少,数据计算更少;利用了JavaScript的异步机制,使用较少的代码较为自然的实现了去抖功能。 |
@hanzichi 韩老师您的文中还有一处小小的笔误:
第一行注释中,wait seconds是否应该改为wait milliseconds? |
@gdh1995 请问为什么匿名函数timer不会被清理?区别在哪里呢? |
underscore版虽然不用每次触发时都清除计时器,但是每次触发时也使用Date对象重新生成了一个时间戳呀。 |
前文 我们对 JavaScript 中的函数节流和函数去抖的概念和应用场景进行了简单的了解,本文我们来深入探究下函数去抖的实现。(不懂函数去抖概念的建议看下前文 JavaScript 函数节流和函数去抖应用场景辨析 )
我们以 scroll 事件为例,探究如何实现滚动一次窗口打印一个 hello world 字符串。
如果不对其进行节流或者去抖控制:
这样每滚动一次,实际上会打印 N 多个 hello world。函数去抖背后的基本思想是指,某些代码不可以在没有间断的情况连续重复执行。第一次调用函数,创建一个定时器,在指定的时间间隔之后运行代码。当第二次调用该函数时,它会清除前一次的定时器并设置另一个。如果前一个定时器已经执行过了,这个操作就没有任何意义。然而,如果前一个定时器尚未执行,其实就是将其替换为一个新的定时器。目的是只有在执行函数的请求停止了一段时间之后才执行。
《高程三》给出了最简洁最经典的去抖代码(书中说是节流,实则为去抖),调用如下:
在窗口内滚动一次,停止,1000ms 后,打印了 hello world,因为我们设置了一个 1000ms 延迟的定时器,细思非常巧妙。
underscore 在其基础上进行了扩充,直接看代码,含大量注释:
等等,一下子多了这么多代码,那么我们比基础版多了哪些功能(优势)呢?
首先,基础版能做的,我们一样能做,一样让它在连续滚动后停止的 1000ms 后打印 hello world:
我们还可以在滚动刚触发的时候打印字符串,而不是连续滚动结束后,只需传入第三个参数,会自动忽略第二个参数:
这样对于连续的滚动,也只会打印一次,但是是在事件第一次触发的时候。
回调函数需要传入参数?一点问题都没有。
当然,除了功能上的优势,性能也是提高不少,最显而易见的是基础版每此触发事件都会取消定时器,然后重新设置定时器,而 underscore 中会在一定时间后才
取消定时器,重新设置定时器。其他更多可以细究下源码。(对性能有兴趣的可以看看这个 pr https://github.com/jashkenas/underscore/pull/1269)The text was updated successfully, but these errors were encountered: