一,区别
防抖:在执行时间内,需要触发的事件会不断的延后。
节流:在执行时间内,固定频率触发事件。
二,防抖
理解:当用户频繁操作时,比如输入,不每输一个字母就去请求一次网络,而是在用户停下操作的一段固定时间后,再请求网络,如果这段固定的时间内用户又操作了,那么这段时间清空,网络请求也会被推迟,这样可以减少请求次数,减轻服务器压力。
防抖函数主要是对事件触发频率非常高的函数,对它来进行一个优化,相当于一个性能优化的一部分。
1.原理
利用了定时器和闭包的不回收机制原理。在一段时间后执行响应事件函数,如果在这段时间内再次被调用,那么会清理前一次的定时器,重新开始计算执行时间,直到在响应时间内没有再次调用该函数时,则执行响应事件,就是只执行了最后一次调用。
2.应用场景
- 输入框中频繁的输入内容,搜索或提交信息;
- 频繁点击按钮,触发某个事件;
- 监听浏览器滚动事件,完成某些特定的操作;
- 用户缩放浏览器的resize事件等。
3.实现
(1)基础实现
封装一个函数时,1.要考虑需要接收什么参数,基础实现防抖接收参数一:回调的函数,参数二:延迟时间;2.有什么返回值,这里返回一个函数;3.内部实现。
整体思路是拿到上一次的timeout。
根据防抖原理,只执行最后一次操作,需要清理之前的操作。也就是第二次操作时,需要拿到第一次操作时定时器的timeout,但是timeout不能定义在return的函数中,因为这样的话它就是一个局部变量,局部变量在执行完这个函数之后,如果没有一个对应的引用值,那它就会被销毁,所以,在这个函数的外部定义timeout,在函数内部引用外部的timeout变量,会形成闭包,只要debounce()函数不销毁,那么这个闭包也会一直存在。而这个函数绑定在了onclick上,是不会被销毁的。
注意:debounce函数是个独立调用,所以它的this是指向window,所以return的那个函数不要使用箭头函数,箭头函数没有自己的this,那么就会沿着作用域链往上找,找到debounce函数的this,这样的话获取不到当前dom的属性。所以用function(){}的话,返回的这个函数就会绑定到onclick方法上,那么它的this就会指向这个dom。
this指向就是谁调指谁。
(2)优化实现
优化一:响应事件函数的this的指向
dom节点在触发事件的时候,this应该是要指向dom本身(如button)才能获取到某些值,比如this.value,获取input输入框的值,而在基础实现中,事件绑定时该响应事件函数执行时是独立函数(func()),this的指向是window(如下图),所以需要优化改变this的指向。
根据基础实现可知,返回的匿名函数中的this指向button,(12月14日修改:可以不用把this存到that中,因为setTimout那里使用的是箭头函数,下图多了一步,懒得改了哈。)
改变this指向:apply,call,bind,new.
***当setTimeOut使用的是箭头函数时,箭头函数没有自己的this(但是箭头函数在创建的时候会绑定一个this),所以会沿着作用域链向上寻找this。
优化二:获取event参数
12月14号添加说明:下图中使用的是arguments,其实可以直接写return function(...args),使用剩余参数,是个数组。
优化以上两点在面试中写出来已经很好了,以下优化作为补充学习。
优化三:增加一个取消功能
场景:当用户在输入框输入时点击了其它功能,这个操作时间在防抖设定的时间之内,此时已经切换页面了,那么响应函数也不应该再调用,所以需要一个取消定时器功能。
优化四:是否立即执行
优化五:拿到返回结果
直接引用了封装好的文件
优化完成,封装好的防抖函数:
function debounce (func, wait, immediate = false, resultCallback) {
let timeout
let isInvoke = false
const _debounce = function (...args) {
return new Promise((resolve, reject) => {
try {
if (timeout) clearTimeout(timeout)
let res
if (immediate && !isInvoke) {
res = func.apply(this, args)
if (resultCallback)resultCallback = res
resolve(res)
isInvoke = true
return
}
timeout = setTimeout(() => {
res = func.apply(this, args)
if (resultCallback)resultCallback = res
resolve(res)
timeout = null
isInvoke = false
}, wait)
} catch (error) {
reject(error)
}
})
}
_debounce.cancel = function () {
if (timeout) {
clearTimeout(timeout)
timeout = null
isInvoke = false
}
}
return _debounce
}
使用:
注意,因为返回的是个函数,所以需要手动调一次。
三,节流
规定的固定时间内就要执行。
1.原理
在执行时间内,固定频率触发事件
2.应用场景
- 监听页面的滚动事件;
- 鼠标移动事件;
- 用户频繁点击按钮操作;
- 游戏中的一些设计,技能冷却;
3.实现
(1)时间戳实现
等待时间 = 间隔时间 -(当前时间 - 开始时间)
开始时间变量要定义在返回函数外面,这样返回函数里面调用外面的变量,形成闭包,那么这个startTime就不会被销毁。
function throttle(func, interval){
let startTime = 0
const _throttle = function(..args){
let nowTime = new Date().getTime() //获取当前时间戳
//计算需要等待的事件执行函数
let waitTime = interval - (nowTime - startTime)
if(waitTime <= 0){
func.apply(this,args)
startTime = nowTime
}
}
return _throttle
}
优化一:立即执行
(2)定时器实现
function throttle(func,interval){
let timer = null
const _throttle = function(...args){
if(!timer){
timer = setTimeout(()=>{
func.apply(this,args)
timer = null
},interval)
}
}
return _throttle
}
四、其它:underscore使用(封装好的库)
CDN引入: