js

手写系列--防抖和节流

手写防抖和节流

Posted by AzirKxs on 2022-11-06
Estimated Reading Time 6 Minutes
Words 1.3k In Total
Viewed Times

手写系列-防抖和节流

防抖和节流都是优化高频率执行代码的一种手段,例如浏览器的resize,scroll,mousemove等事件在触发时,会频繁的调用绑定在事件上的回调函数,极大的浪费资源,降低了前端的性能,因此就需要使用防抖和节流来优化代码

防抖:n秒后在执行该事件,若在n秒内重复触发,则重新计时

节流:n秒内只运行一次,若在n秒内重复触发,只有一次生效

防抖

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}

.box {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background-color: gray;
}
</style>
</head>

<body>
<div class="box"></div>
<script>
window.addEventListener('load', function () {
let box = document.querySelector('.box');
let count = 0;
box.addEventListener('mousemove', function () {
box.textContent = count++;
})
})
</script>
</body>

</html>

debounce

如图所示,鼠标移动count+1,设想如果触发的是ajax请求,就太过频繁了

接下来改进代码:

防抖代码

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}

.box {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background-color: gray;
}
</style>
</head>

<body>
<div class="box"></div>
<script>
window.addEventListener('load', function () {
let box = document.querySelector('.box');
let count = 0;
function getUserAction() {
box.textContent = count++;
}
box.addEventListener('mousemove', debounce(getUserAction, 1000));
})

function debounce(func, time) {
let timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(func, time);
}
}
</script>
</body>

</html>

debounce2

修复this

由于func函数的调用位置发生了改变,使得func的this指向了window而不是box,因此我们需要改变func中this的指向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
window.addEventListener('load', function () {
let box = document.querySelector('.box');
let count = 0;

function getUserAction() {
console.log(this)
box.textContent = count++;
}

function debounce(func, time) {
let timeout;
let context;
return function () {
context = this;
clearTimeout(timeout);
timeout = setTimeout(function(){
func.apply(context)
}, time);
}
}

box.addEventListener('mousemove',debounce(getUserAction,1000))})

修复event

此时在getUserAction中打印event位undefined,而我们希望event可以为mouseEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
window.addEventListener('load', function () {
let box = document.querySelector('.box');
let count = 0;

function getUserAction(e) {
console.log(this) //box
console.log(e) //MouseEvent
box.textContent = count++;
}

function debounce(func, time) {
let timeout;
let context,args;
return function () {
context = this;
args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function(){
func.apply(context,arguments)
}, time);
}
}

box.addEventListener('mousemove',debounce(getUserAction,1000))})

立即执行

我们不希望非要等到事件停止触发后才执行,我希望立刻执行函数,然后等到停止触发 n 秒后,才可以重新触发执行

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
window.addEventListener('load', function () {
let box = document.querySelector('.box');
let count = 0;
function getUserAction(e) {
console.log(this, e)
box.textContent = count++;
}
function debounce(func, time, immediate) {
let timeout;
let context,args;
return function () {
context = this;
args = arguments;
console.log(arguments)
if(timeout) clearTimeout(timeout);
if (immediate) {
let callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, time);
if(callNow) func.apply(context,args)
} else {
timeout = setTimeout(function () {
func.apply(context, args)
}, time);
}
}
}
box.addEventListener('mousemove', debounce(getUserAction, 1000,true))
})

节流

如果持续触发事件,每隔一段时间只执行一次事件

使用时间戳

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
window.addEventListener('load', function () {
let box = document.querySelector('.box');
let count = 0;
function getUserAction() {
box.textContent = count++;
}

box.addEventListener('mousemove',throttle(getUserAction,1000))

function throttle(func,time){
let previous = 0;
let context,args;
return function(){
let now = +new Date();
context = this;
args = arguments;
if (now - previous >= time){
func.apply(func,args)
previous = now
}
}
}
})

使用定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
window.addEventListener('load', function () {
let box = document.querySelector('.box');
let count = 0;
function getUserAction() {
box.textContent = count++;
}

box.addEventListener('mousemove',throttle(getUserAction,1000))

function throttle(func,time){
let context,args;
let timeout;
return function(){
context = this;
args = arguments;
if(!timeout){
timeout = setTimeout(function(){
timeout = null;
func.apply(context,args)
},time)
}
}
}
})

合体

对比上面两种方式,我们可以发现:

  1. 第一种事件会立刻执行,第二种事件会在 n 秒后第一次执行
  2. 第一种事件停止触发后没有办法再执行事件,第二种事件停止触发后依然会再执行一次事件

如果我们既需要事件立刻执行,又需要停止触发后依然会再次执行一次事件,那么就可以二者结合

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
window.addEventListener('load', function () {
let box = document.querySelector('.box');
let count = 0;
function getUserAction() {
box.textContent = count++;
}

box.addEventListener('mousemove',throttle(getUserAction,1000))

function throttle(func,time){
let context,args,timeout;
let previous = 0;

function throttled(){
let now = +new Date();
let remain = time - (now - previous);
context = this;
args = arguments;
if(remain <= 0 || remain > time){
if(timeout){
clearTimeout(timeout);
timeout = null;
}
func.apply(context,args)
previous = now;
}else if(!timeout){
timeout = setTimeout(function(){
previous = +new Date();
timeout = null;
func.apply(context,args)
},time)
}
}

return throttled;
}
})

如果这篇文章对你有帮助,可以bilibili关注一波 ~ !此外,如果你觉得本人的文章侵犯了你的著作权,请联系我删除~谢谢!