Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f6bf966

Browse files
添加JavaScript专题之跟着underscore学节流
1 parent 53abe2d commit f6bf966

10 files changed

Lines changed: 478 additions & 0 deletions

File tree

‎Images/throttle/throttle1.gif‎

12.4 KB
Loading[フレーム]

‎Images/throttle/throttle2.gif‎

15.5 KB
Loading[フレーム]

‎Images/throttle/throttle3.gif‎

15.7 KB
Loading[フレーム]
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# JavaScript专题之跟着 underscore 学节流
2+
3+
## 前言
4+
5+
[《JavaScript专题之跟着underscore学防抖》](https://github.com/mqyqingfeng/Blog/issues/22)中,我们了解了为什么要限制事件的频繁触发,以及如何做限制:
6+
7+
1. debounce 防抖
8+
2. throttle 节流
9+
10+
今天重点讲讲节流的实现。
11+
12+
## 节流
13+
14+
节流的原理很简单:
15+
16+
如果你持续触发事件,每隔一段时间,只执行一次事件。
17+
18+
根据首次是否执行以及结束后是否执行,效果有所不同,实现的方式也有所不同。
19+
我们用 leading 代表首次是否执行,trailing 代表结束后是否再执行一次。
20+
21+
关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。
22+
23+
## 使用时间戳
24+
25+
让我们来看第一种方法:使用时间戳,当触发事件的时候,我们取出当前的时间戳,然后减去之前的时间戳(最一开始值设为 0 ),如果大于设置的时间周期,就执行函数,然后更新时间戳为当前的时间戳,如果小于,就不执行。
26+
27+
看了这个表述,是不是感觉已经可以写出代码了...... 让我们来写第一版的代码:
28+
29+
```js
30+
// 第一版
31+
function throttle(func, wait) {
32+
var context, args;
33+
var previous = 0;
34+
35+
return function() {
36+
var now = +new Date();
37+
context = this;
38+
args = arguments;
39+
if (now - previous > wait) {
40+
func.apply(context, args);
41+
previous = now;
42+
}
43+
}
44+
}
45+
```
46+
47+
例子依然是用讲 debounce 中的例子,如果你要使用:
48+
49+
```js
50+
container.onmousemove = throttle(getUserAction, 1000);
51+
```
52+
53+
效果演示如下:
54+
55+
![使用时间戳](https://github.com/mqyqingfeng/Blog/raw/master/Images/throttle/throttle1.gif)
56+
57+
我们可以看到:当鼠标移入的时候,事件立刻执行,每过 1s 会执行一次,如果在 4.2s 停止触发,以后不会再执行事件。
58+
59+
## 使用定时器
60+
61+
接下来,我们讲讲第二种实现方式,使用定时器。
62+
63+
当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
64+
65+
```js
66+
// 第二版
67+
function throttle(func, wait) {
68+
var timeout;
69+
var previous = 0;
70+
71+
return function() {
72+
context = this;
73+
args = arguments;
74+
if (!timeout) {
75+
timeout = setTimeout(function(){
76+
timeout = null;
77+
func.apply(context, args)
78+
}, wait)
79+
}
80+
81+
}
82+
}
83+
```
84+
85+
为了让效果更加明显,我们设置 wait 的时间为 3s,效果演示如下:
86+
87+
![使用定时器](https://github.com/mqyqingfeng/Blog/raw/master/Images/throttle/throttle2.gif)
88+
89+
我们可以看到:当鼠标移入的时候,事件不会立刻执行,晃了 3s 后终于执行了一次,此后每 3s 执行一次,当数字显示为 3 的时候,立刻移出鼠标,相当于大约 9.2s 的时候停止触发,但是依然会在第 12s 的时候执行一次事件。
90+
91+
所以比较两个方法:
92+
93+
1. 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
94+
2. 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件
95+
96+
## 双剑合璧
97+
98+
那我们想要一个什么样的呢?
99+
100+
有人就说了:我想要一个有头有尾的!就是鼠标移入能立刻执行,停止触发的时候还能再执行一次!
101+
102+
所以我们综合两者的优势,然后双剑合璧,写一版代码:
103+
104+
```js
105+
// 第三版
106+
function throttle(func, wait) {
107+
var timeout, context, args, result;
108+
var previous = 0;
109+
110+
var later = function() {
111+
previous = +new Date();
112+
timeout = null;
113+
func.apply(context, args)
114+
};
115+
116+
var throttled = function() {
117+
var now = +new Date();
118+
//下次触发 func 剩余的时间
119+
var remaining = wait - (now - previous);
120+
context = this;
121+
args = arguments;
122+
// 如果没有剩余的时间了或者你改了系统时间
123+
if (remaining <= 0 || remaining > wait) {
124+
if (timeout) {
125+
clearTimeout(timeout);
126+
timeout = null;
127+
}
128+
previous = now;
129+
func.apply(context, args);
130+
} else if (!timeout) {
131+
timeout = setTimeout(later, remaining);
132+
}
133+
};
134+
return throttled;
135+
}
136+
```
137+
138+
效果演示如下:
139+
140+
![throttle3](images/throttle/throttle3.gif)
141+
142+
我们可以看到:鼠标移入,事件立刻执行,晃了 3s,事件再一次执行,当数字变成 3 的时候,也就是 6s 后,我们立刻移出鼠标,停止触发事件,9s 的时候,依然会再执行一次事件。
143+
144+
## 优化
145+
146+
但是我有时也希望无头有尾,或者有头无尾,这个咋办?
147+
148+
那我们设置个 options 作为第三个参数,然后根据传的值判断到底哪种效果,我们约定:
149+
150+
leading:false 表示禁用第一次执行
151+
trailing: false 表示禁用停止触发的回调
152+
153+
我们来改一下代码:
154+
155+
```js
156+
// 第四版
157+
function throttle(func, wait, options) {
158+
var timeout, context, args, result;
159+
var previous = 0;
160+
if (!options) options = {};
161+
162+
var later = function() {
163+
previous = options.leading === false ? 0 : new Date().getTime();
164+
timeout = null;
165+
func.apply(context, args);
166+
if (!timeout) context = args = null;
167+
};
168+
169+
var throttled = function() {
170+
var now = new Date().getTime();
171+
if (!previous && options.leading === false) previous = now;
172+
var remaining = wait - (now - previous);
173+
context = this;
174+
args = arguments;
175+
if (remaining <= 0 || remaining > wait) {
176+
if (timeout) {
177+
clearTimeout(timeout);
178+
timeout = null;
179+
}
180+
previous = now;
181+
func.apply(context, args);
182+
if (!timeout) context = args = null;
183+
} else if (!timeout && options.trailing !== false) {
184+
timeout = setTimeout(later, remaining);
185+
}
186+
};
187+
return throttled;
188+
}
189+
```
190+
191+
## 取消
192+
193+
在 debounce 的实现中,我们加了一个 cancel 方法,throttle 我们也加个 cancel 方法:
194+
195+
```js
196+
// 第五版 非完整代码,完整代码请查看最后的演示代码链接
197+
...
198+
throttled.cancel = function() {
199+
clearTimeout(timeout);
200+
previous = 0;
201+
timeout = null;
202+
}
203+
...
204+
```
205+
206+
## 注意
207+
208+
我们要注意 underscore 的实现中有这样一个问题:
209+
210+
那就是 `leading:false``trailing: false` 不能同时设置。
211+
212+
如果同时设置的话,比如当你将鼠标移出的时候,因为 trailing 设置为 false,停止触发的时候不会设置定时器,所以只要再过了设置的时间,再移入的话,就会立刻执行,就违反了 leading: false,bug 就出来了,所以,这个 throttle 只有三种用法:
213+
214+
```js
215+
container.onmousemove = throttle(getUserAction, 1000);
216+
container.onmousemove = throttle(getUserAction, 1000, {
217+
leading: false
218+
});
219+
container.onmousemove = throttle(getUserAction, 1000, {
220+
trailing: false
221+
});
222+
```
223+
224+
至此我们已经完整实现了一个 underscore 中的 throttle 函数,恭喜,撒花!
225+
226+
## 演示代码
227+
228+
相关的代码可以在 [Github 博客仓库](https://github.com/mqyqingfeng/Blog/tree/master/demos/throttle) 中找到
229+
230+
## 专题系列
231+
232+
JavaScript专题系列目录地址:[https://github.com/mqyqingfeng/Blog](https://github.com/mqyqingfeng/Blog)
233+
234+
JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。
235+
236+
如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

‎demos/throttle/index.html‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE html>
2+
<html lang="zh-cmn-Hans">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<meta http-equiv="x-ua-compatible" content="IE=edge, chrome=1">
7+
<title>throttle</title>
8+
<style>
9+
#container{
10+
width: 100%;
11+
height: 200px;
12+
line-height: 200px;
13+
text-align: center;
14+
color: #fff;
15+
background-color: #444;
16+
font-size: 30px;
17+
}
18+
</style>
19+
</head>
20+
21+
<body>
22+
<div id="container"></div>
23+
<button id="button">点击取消debounce</button>
24+
<script src="throttle4.js"></script>
25+
</body>
26+
27+
</html>
28+
29+

‎demos/throttle/throttle1.js‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* 第一版 使用时间戳
3+
*/
4+
5+
var count = 1;
6+
var container = document.getElementById('container');
7+
8+
function getUserAction() {
9+
container.innerHTML = count++;
10+
};
11+
12+
container.onmousemove = throttle(getUserAction, 1000);
13+
14+
function throttle(func, wait) {
15+
var context, args;
16+
var previous = 0;
17+
18+
return function() {
19+
var now = +new Date();
20+
context = this;
21+
args = arguments;
22+
if (now - previous > wait) {
23+
func.apply(context, args);
24+
previous = now;
25+
}
26+
}
27+
}

‎demos/throttle/throttle2.js‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* 第二版 使用定时器
3+
*/
4+
var count = 1;
5+
var container = document.getElementById('container');
6+
7+
function getUserAction() {
8+
container.innerHTML = count++;
9+
};
10+
11+
container.onmousemove = throttle(getUserAction, 3000);
12+
13+
function throttle(func, wait) {
14+
var timeout;
15+
var previous = 0;
16+
17+
return function() {
18+
context = this;
19+
args = arguments;
20+
if (!timeout) {
21+
timeout = setTimeout(function(){
22+
timeout = null;
23+
func.apply(context, args)
24+
}, wait)
25+
}
26+
27+
}
28+
}

‎demos/throttle/throttle3.js‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* 第三版 有头有尾
3+
*/
4+
var count = 1;
5+
var container = document.getElementById('container');
6+
7+
function getUserAction() {
8+
container.innerHTML = count++;
9+
};
10+
11+
container.onmousemove = throttle(getUserAction, 3000);
12+
13+
function throttle(func, wait) {
14+
var timeout, context, args, result;
15+
var previous = 0;
16+
17+
var later = function() {
18+
previous = +new Date();
19+
timeout = null;
20+
func.apply(context, args)
21+
};
22+
23+
var throttled = function() {
24+
var now = +new Date();
25+
//下次触发func剩余的时间
26+
var remaining = wait - (now - previous);
27+
context = this;
28+
args = arguments;
29+
// 如果没有剩余的时间了或者你改了系统时间
30+
if (remaining <= 0 || remaining > wait) {
31+
if (timeout) {
32+
clearTimeout(timeout);
33+
timeout = null;
34+
}
35+
previous = now;
36+
func.apply(context, args);
37+
} else if (!timeout) {
38+
timeout = setTimeout(later, remaining);
39+
}
40+
};
41+
42+
43+
return throttled;
44+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /