手写系列-防抖和节流
防抖和节流都是优化高频率执行代码的一种手段,例如浏览器的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>
|

如图所示,鼠标移动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>
|

修复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) console.log(e) 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) } } } })
|
合体
对比上面两种方式,我们可以发现:
- 第一种事件会立刻执行,第二种事件会在 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
| 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关注一波 ~ !此外,如果你觉得本人的文章侵犯了你的著作权,请联系我删除~谢谢!