节流和防抖都是为了提升网页性能的技术。监听用户触发的事件往往被触发的频次较高或者需要消耗较高的性能(例如发送ajax请求查询数据库中的内容),因此需要降低事件被触发的频次。
参考:节流和防抖的区别,以及如何实现
防抖
防抖的含义是:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。
实现方式: 每次触发事件时设置一个延迟调用方法,并且取消之前的延时调用方法
缺点: 如果事件在规定的时间间隔内被不断的触发,则调用方法会被不断的延迟
应用场景: 不会连续触发的事件,例如用户输入。
function debounce(fn) {
// 通过闭包保存时间标记
let timeout = null
return function () {
// 再次触发该函数的时候把上一次的setTimeOut清除
clearTimeout(timeout)
timeout = setTimeout(()=>{
// 由于箭头函数没有this,所以会向上一层作用域寻找this,也就是上一层的匿名function的this,指向调用function的对象
// argument是传入匿名function的实参
fn.apply(this,arguments)
},200)
}
}
应用示例
<input type="text" id="inp">
// 要执行的事件
function myFunction(e) {
console.log(e.target)
}
// 绑定给input DOM
document.getElementById("inp").addEventListener('click',debounce(myFunction))
这样一来,在inp元素里停止输入的100ms之后,就会触发相应的事件myFunction
。如果一直以间隔小于200ms的速度输入,那么就一直不会触发myFunction
,直到停止输入200ms后。
假如myFunction是一个比较消耗资源的操作,例如频繁向后端发送ajax请求,会给后端带来一定的压力,同时接收到后端返回的结果前端也会用一定的回调进行处理,因此对前端性能也有影响,这时候用防抖就显得比较重要了。
如果这里采用节流而不是防抖,无论用户输入多快都会固定200ms发送一次ajax请求;如果采用防抖,遇到手速比较快的用户,比较一秒钟能键入5次以上的用户,那么会等他一阵输出完了之后再发送一次ajax请求。
理解
JS脚本在执行到这一行的时候:
document.getElementById("inp").addEventListener('click',debounce(myFunction))
首先获取到对应的DOM,然后执行debounce(myFunction)
这个语句,进入到debounce函数体:
function debounce(fn) {
// 通过闭包保存时间标记
let timeout = null
return function () {
// 再次触发该函数的时候把上一次的setTimeOut清除
clearTimeout(timeout)
timeout = setTimeout(()=>{
// 由于箭头函数没有this,所以会向上一层作用域寻找this,也就是上一层的匿名function的this,指向调用function的对象
// argument是传入匿名function的实参
fn.apply(this,arguments)
},500)
}
}
首先,timeout的这个变量被创建,而且在下面定义的匿名函数里被调用,这个匿名函数被return出去绑定给DOM元素,由于闭包的缘故timeout变量会一直保留在进程里,直到DOM元素被销毁。
let timeout = null
每次在inp元素里键入都会触发这个匿名函数,首先清除上一次绑定的timeout
变量指向的延时事件,然后创建新的延时事件,赋值给timeout
变量。
fn.apply(this,arguments) 里的this,由于箭头函数没有自己的this,所以会向上一层作用域寻找this,也就是上一层的匿名function的this,指向调用function的对象(即绑定的DOM对象);arguments则是匿名函数function的实参,是一个event对象,作为参数传给fn。
function () {
// 再次触发该函数的时候把上一次的setTimeOut清除
clearTimeout(timeout)
timeout = setTimeout(()=>{
// 由于箭头函数没有this,所以会向上一层作用域寻找this,也就是上一层的匿名function的this,指向调用function的对象
// argument是传入匿名function的实参
fn.apply(this,arguments)
},500)
}
节流
节流的含义是:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。
与防抖的区别是:同样的时间间隔500ms,节流保证在500ms内一定会执行回调,而在防抖的场景下,如果触发的时间间隔小于500ms,则不会执行回调,直到触发间隔大于500ms。
实现方式: 设置一个变量flag来记录是否有指定回调函数在等待调用
应用场景: 会连续触发的事件,例如用户手指滑动、鼠标滚轮滚动。如果用户手指滑动采用防抖,那么直到用户手指停下,才会执行回调,给人的感觉就是很卡。
function throttle(fn) {
// 是否有指定回调函数在等待调用
let flag = false
return function() {
if(flag === true) {
// 如果已经有回调函数在等待调用,直接return
return
}
flag = true
let timer = setTimeout(()=>{
fn.apply(this,arguments)
// 回调函数执行后,重新把flag置为false
flag = false
},500)
}
}
应用示例
// 要执行的事件
function myFunction(e) {
console.log(e.target)
}
// 绑定给window
window.addEventListener('scroll',debounce(myFunction))
这样用户滚动的时候,会以500ms的频次回调函数。当然这样会比较卡。浏览器每隔16ms刷新一帧,所以可以将setTimeOut的时间设为16,这样既不浪费性能,也不会让用户感觉到卡顿。