函数防抖和函数节流
1、什么是函数防抖和函数节流
防抖(debounce
)和节流(throttle
)都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似不相同,基本思想都是某些代码不可以在没有间断的情况下连续重复执行
1.1 函数防抖
如果一个事件被频繁执行多次,并且触发的时间间隔过短,则防抖函数可以使得对应的事件处理函数,只执行最后触发的一次。函数防抖可以把多个顺序的调用合并成一次。
1.2 函数节流
如果一个事件被频繁触发多次,节流函数可以按照固定的频率去执行相应的事件处理方法。函数节流保证一个事件一定事件内只能执行一次。
2、应用
总的来说,函数防抖适合多次事件一次响应的情况,函数节流适合大量事件按照时间做平均分配触发
3、实现
3.1 实现函数防抖
function debounce(fn, wait) { var timer = null; // 返回函数对debounce作用域形成闭包 return function () { var context = this var args = arguments if (timer) { // 事件被触发,清除timer并重新开始计时 clearTimeout(timer); timer = null; } timer = setTimeout(function () { fn.apply(context, args) }, wait) } } var fn = function () { console.log('boom') } setInterval(debounce(fn,500),1000) // 第一次在1500ms后触发,之后每1000ms触发一次 setInterval(debounce(fn,2000),1000) // 不会触发一次(把函数防抖看出技能读条,如果读条没完成就用技能,便会失败而且重新读条)
1、第一次调用函数的时候创建了一个定时器,在指定的时间之后运行代码
2、当第二次调用该函数的时候,会清除前一次的定时器并设置另一个
3、如果前一个定时器已经执行过了,这个操作就没有任何意义
4、当前一个定时器没有执行的时候,就是将他替换成一个新的定时器
5、目的是只有在执行函数的请求停止了wait时间之后才执行
3.2 实现函数节流
1)利用时间戳实现
// fn 是需要执行的函数 // wait 是时间间隔 const throttle = (fn, wait = 50) => { // 上一次执行 fn 的时间 let previous = 0 // 将 throttle 处理结果当作函数返回 return function(...args) { // 获取当前时间,转换成时间戳,单位毫秒 let now = +new Date() // 将当前时间和上一次执行函数的时间进行对比 // 大于等待时间就把 previous 设置为当前时间并执行函数 fn if (now - previous > wait) { previous = now fn.apply(this, args) } } } // DEMO // 执行 throttle 函数返回新函数 const betterFn = throttle(() => console.log('fn 函数执行了'), 1000) // 每 10 毫秒执行一次 betterFn 函数,但是只有时间差大于 1000 时才会执行 fn setInterval(betterFn, 10)
2) 利用定时器实现
function throttle(fn, threshold, scope) { let timer; return function () { let context = scope || this, args = arguments; if (!timer) { timer = setTimeout(function () { fn.apply(context, args); timer = null; }, threshold) } } }
4、实例(scro
ll
事件)
<!-- CSS样式 --> <style> .wrap { width: 200px; height: 330px; margin: 50px; margin-top: 200px; position: relative; float: left; } .header { width: 100%; height: 30px; background-color: #a8d4f4; text-align: center; line-height: 30px; } .container { background-color: darkseagreen; box-sizing: content-box; width: 200px; height: 300px; overflow: scroll; position: relative; } .content { width: 140px; height: 800px; margin: auto; background-color: cadetblue; } </style> <!-- html --> <body> <div class="wrap"> <div class="header">滚动事件:普通</div> <div class="container"> <div class="content"></div> </div> </div> <div class="wrap"> <div class="header">滚动事件:<strong>加了函数防抖</strong></div> <div class="container"> <div class="content"></div> </div> </div> <div class="wrap"> <div class="header">滚动事件:<strong>加了函数节流</strong></div> <div class="container"> <div class="content"></div> </div> </div> </body>
let els = document.getElementsByClassName('container'); let count1 = 0, count2 = 0, count3 = 0; const THRESHOLD = 200; els[0].addEventListener('scroll', function handle() { console.log('普通滚动事件!count1=', ++count1); }); els[1].addEventListener('scroll', debounce(function handle() { console.log('执行滚动事件!(函数防抖) count2=', ++count2); }, THRESHOLD)); els[2].addEventListener('scroll', throttle(function handle() { console.log(Date.now(), ', 执行滚动事件!(函数节流) count3=', ++count3); }, THRESHOLD)); // 函数防抖 function debounce(fn, delay, scope) { let timer = null; let count = 1; return function () { let context = scope || this, args = arguments; clearTimeout(timer); console.log(Date.now(), ", 触发第", count++, "次滚动事件!"); timer = setTimeout(function () { fn.apply(context, args); console.log(Date.now(), ", 可见只有当高频事件停止,最后一次事件触发的超时调用才能在delay时间后执行!"); }, delay); } } // 函数节流 function throttle(fn, threshold, scope) { let timer; let prev = Date.now(); return function () { let context = scope || this, args = arguments; let now = Date.now(); if (now - prev > threshold) { prev = now; fn.apply(context, args); } } }
运行结果: