对于项目优化这块,防抖节流是很重要的一part
在掘金阅读文章-《跟着大佬学防抖》觉得太赞了,自己也想写一篇类似的,和大家一起学习
也因为上述的作者原因,我也去掘金、github上面看大佬的文章,写的真是太好了,下面开启学习之路。
【这篇文章我会大部分跟随作者的步伐,以及加入自己练习的demo进行分析总结】
跟随大佬:冴羽
跟随原文链接: debounce | throttle
debounce(防抖)
前言
在前端开发中会遇到一些频繁的事件触发,比如:
window
的resize
、scroll
mousedown
、mousemove
keyup
、keydown
- …
为此,我们举个示例代码来了解事件是如何地频繁触发的
index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>debouse</title>
<style>
#container{
width: 100%;
height: 300px;
line-height: 300px;
text-align: center;
background-color: pink;
color:#fff;
font-size: 30px;
font-weight: bolder;
}
</style>
</head>
<body>
<div id='container'>1</div>
<script src='debouse.js'></script>
</body>
</html>
debouse.js
var count=1
var container=document.getElementById('container')
function getUserAction(){
container.innerHTML=count++
}
container.onmousemove=getUserAction
看效果
从左滑到右(可能我中间有停顿),我是触发了205次getUserAction
函数!
这个栗子比较简单,所以浏览器完全反应的过来,可如果是复杂的回调函数或者是ajax呢?【浏览器可能会反应不过来,出现卡顿】
大佬提出了个假设,1秒内触发了60次,每个回调必须在1000/60=16.67ms内完成,否则就会有卡顿出现。【这个是真的比较难,短时间内要求回调完成,就有大概率会出现卡顿】
`
为了解决这个问题,一般有两种解决方案:
- debounce 防抖
- throttle 节流
防抖
防抖的实现
防抖的原理:你尽管触发事件,但是我一定在事件触发n秒后才执行
,如果你在一个事件触发的n秒内又触发了这个事件
,那我们就以新的事件
的时间为准,n秒后才执行,总之,就是要等你触发完事件,n秒内不再触发事件,我才执行。
第一版
function debounce(func,wait){
var timeout;
return function(){
clearTimeout(timeout)
timeout=setTimeout(func,wait)
}
}
用到刚才的案例则是:
container.onmousemove=debounce(getUserAction,1000)
随便怎么移动,在1000ms内也不再触发
从200多次降到一次,现在继续完善
第二版-this
如果我们在getUserAction
函数中console.log(this)
,在不使用debouse
函数的时候,this
的值为:
<div id='container'>1</div>
如果加了debouse之后,this的指向就是window对象,【因为setTimout定义的时候是在window环境下的】
解决方案:
function debounce(func,wait){
var timeout;
return function(){
var context=this
clearTimeout(timeout)
timeout=setTimeout(function(){
//相当于调用了func函数,并且改变了this的指向
func.apply(context)
},wait)
}
}
加完之后,this的指向就变回调用他的元素
第三版----event对象
js在事件处理函数中会提供事件对象event,我们把它打印出来看一下
function getUserAction(e){
console.log(e)
container.innerHTML=count++
}
在有debounce函数的情况下,打印出来的是undefined
,
But,在没有debounce函数的情况下,打印出来的是MouseEvent
对象
SO,修改代码继续
function debounce(func,wait){
var timeout;
return function(){
var context=this
var args=arguments
clearTimeout(timeout)
timeout=setTimeout(function(){
func.apply(context,args)
},wait)
}
}
补充:
getUserAction
函数里的arguments类数组对象
值如图所示
apply方法
1.第一个参数是改变this的指向
2.第二个参数是数组参数,传递给前面调用它函数所用
上面的代码解决了问题,让函数内可以出现原本的MouseEnter对象
第四版----立即执行
这个时候,代码已经很完善了,但是为了让函数变得更完善,我们还应该思考一个新的需求
这个需求is:
不需要等到事件停止触发后才执行,希望立即执行,然后等到停止触发n秒后,才可以重新触发才行
方法:加一个immediate
参数
function debounce(func,wait,immediate){
var timeout;
return function(){
var context=this
var args=arguments
if(timeout) clearTimeout(timeout)
//触发func从队尾提到队前
//[remember:func同步执行(同步任务)、setTimeout是异步执行]
// 1.callNow的初始值是true,同步立即执行,随后timeout开始执行
//2.wait期间,timeout是一个id数字,所以callNow为false,func在此期间永远不会执行
//3.wait之后,timeout赋值null,callNow为true,func又立即开始执行
if(immediate){
// 如果已经执行过,就不再执行
var callNow=!timeout
timeout=setTimeout(function(){
timeout=null
},wait)
if(callNow) func.apply(context,args)
}
else{
timeout=setTimeout(function(){
func.apply(context,args)
},wait)
}
}
}
效果如下:
第五版 返回值
此时注意一点,就是getUserAction
函数可能是有返回值的,所以我们要返回函数的执行结果,但是当immediate
为false
的时候,因为使用了setTimeout
,我们将func.apply(context,args)的返回值赋给变量,最后再return
的时候,值将会一直是undefined
,所以我们只在immediate
为true
的时候返回函数的执行结果
function debounce(func,wait,immediate){
var timeout,result;
return function(){
var context=this
var args=arguments
if(timeout) clearTimeout(timeout)
//触发func从队尾提到队前
//[remember:func同步执行(同步任务)、setTimeout是异步执行]
// 1.callNow的初始值是true,同步立即执行,随后timeout开始执行
//2.wait期间,timeout是一个id数字,所以callNow为false,func在此期间永远不会执行
//3.wait之后,timeout赋值null,callNow为true,func又立即开始执行
if(immediate){
// 如果已经执行过,就不再执行
var callNow=!timeout
timeout=setTimeout(function(){
timeout=null
},wait)
if(callNow) {
result=func.apply(context,args)
}
}
else{
timeout=setTimeout(function(){
func.apply(context,args)
},wait)
}
return result
}
}
【这个result 是有点难理解的,大佬的解释is:
考虑到func可能会有返回值,虽然平时开发当中比较少接触到,但是作为一个工具库,应该考虑的更全面
】
第六版-取消
需求:希望能取消debouce
函数,比如说debounce
的时间间隔是10秒钟,immediate
为true,这样的话,我只能等10秒后才能重新触发事件,现在我希望能有一个按钮,点击后取消防抖,这样我再去触发,就可以又立刻执行啦。
function debounce(func, wait, immediate) {
var timeout, result;
var debounced = function () {
var context = this;
var args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
// 如果已经执行过,就不再执行
var callNow = !timeout;
timeout = setTimeout(function () {
timeout = null;
}, wait);
if (callNow) {
result = func.apply(context, args);
}
} else {
timeout = setTimeout(function () {
func.apply(context, args);
}, wait);
}
return result;
};
其他函数部分:
function getUserAction() {
container.innerHTML = count++;
}
var setUserAction = debounce(getUserAction, 1000, true);
container.onmousemove = setUserAction;
btn.addEventListener("click", function () {
setUserAction.cancel();
});
撒花✿✿ヽ(°▽°)ノ✿
演示代码
大佬的github仓库
我的练手github仓库