什么是Throttle


本文作者: jsweibo

本文链接: https://jsweibo.github.io/2019/09/21/%E4%BB%80%E4%B9%88%E6%98%AFThrottle/

摘要

本文主要讲述了:

  1. 什么是 Throttle
  2. 模式
  3. underscope 的实现

正文

什么是 Throttle

Throttle(节流)是一种限制函数执行频率的算法。

节流函数被第一次调用时将立即执行,而后节流函数会进入时长为t的冷却期。

冷却期内,如果节流函数被第二次调用,节流函数并不会立即执行,而是直接返回上一次调用的返回值并计划在冷却期结束后再执行第二次调用。第二次执行完成后,节流函数进入新的冷却期。

冷却期内,如果节流函数被第三次调用,由于冷却期结束后已经安排了第二次调用的执行,因此节流函数什么也不做,直接返回上一次调用的返回值。

模式

禁用前沿执行

节流函数被第一次调用时并不会立即执行,而是直接返回上一次调用的返回值并计划在冷却期结束后再执行第一次调用。等到第一次执行完成后,节流函数会进入时长为t的冷却期。

冷却期内,如果节流函数被第二次调用,由于冷却期结束后已经安排了第一次调用的执行,因此节流函数什么也不做,直接返回上一次调用的返回值。

禁用后沿执行

节流函数被第一次调用时将立即执行。而后节流函数会进入时长为t的冷却期。

冷却期内,如果节流函数被第二次调用,节流函数什么也不做,直接返回上一次调用的返回值。

underscope 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
_.throttle = function (func, wait, options) {
/*
* timeout是计时器Id
* context是上下文
* args是参数列表
* result是func()的返回值
*
* previous是上次执行时间,置0表示从未执行过
*
* options是选项
*/
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};

var later = function () {
// 若前沿执行被禁用,将上次执行时间置0以确保下次节流函数被调用时能继续进入冷却期。否则,记录本地时间
previous = options.leading === false ? 0 : _.now();

// 将计时器Id置空
timeout = null;

// 立即执行func()并记录返回值
result = func.apply(context, args);

/*
* 将无用的变量及时手动置空,便于垃圾回收,从而防止内存泄漏
* 前文已经将计时器Id置空,为什么此处还要判断呢?
* 目的是防止在func()中调用了节流函数
* 出处:https://github.com/jashkenas/underscore/issues/2413
*/
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;

// 判断冷却期是否已经结束或用户是否把本地时间修改为过去
if (remaining <= 0 || remaining > wait) {
// 冷却期已结束或用户把本地时间修改为过去

/*
* 如果用户在第一次调用节流函数之后第二次调用节流函数之前,将本机时间修改为过去。如果修改到很久以前且不存在延时调用,那么下一次func()执行要等很久。
* 出处:https://github.com/jashkenas/underscore/pull/1473
*/

// 判断计时器Id是否存在
if (timeout) {
// 计时器Id存在

/*
* setTimeout()只能保证在等待remaining毫秒后执行later(),但无法保证刚好是remaining毫秒,实际可能是更长的时间。
* 此时冷却期已结束,如果此时延时还未执行,表明延时调用已经失去了存在的意义,因此解除。
*/

// 结束延时调用并将计时器Id置空
clearTimeout(timeout);
timeout = null;
}

// 记录func()的执行时间
previous = now;

// 立即执行func()并记录返回值
result = func.apply(context, args);

/*
* 将无用的变量及时手动置空,便于垃圾回收,从而防止内存泄漏
* 前文已经将计时器Id置空,为什么此处还要判断呢?
* 目的是防止在func()中调用了节流函数
* 出处:https://github.com/jashkenas/underscore/issues/2413
*/
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 冷却期未结束且用户未修改过本地时间且不存在延时调用且未禁用后沿执行

// 启动延时调用
timeout = setTimeout(later, remaining);
}

/*
* 注意:不得同时禁用前沿执行和后沿执行
* 出处:https://github.com/jashkenas/underscore/issues/2589
* 禁用前沿执行和后沿执行时,第一个冷却期结束,第三次调用节流函数,执行func(),返回func()的返回值,进入第二个冷却期。此后如果再调用函数,由于previous不再置`0`,相当于只禁用了后沿执行。
*/
return result;
};

throttled.cancel = function () {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};

return throttled;
};

参考资料

本文作者: jsweibo

本文链接: https://jsweibo.github.io/2019/09/21/%E4%BB%80%E4%B9%88%E6%98%AFThrottle/


本文对你有帮助?请支持我


支付宝
微信