手撕防抖与节流

函数防抖(debounce)

在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。

手写防抖

假设我们要点击一个按钮新增一条信息,当然我们不希望每次点击都调用接口新增,我们希望多次点击只新增一次

  • 先简单的模拟一个按钮被点击的过程
// debounce.js
let addBtn=document.getElementById('add')
function addOne(){
console.log('增加一个')
}
addBtn.addEventListener('click',addOne)
  • 因为我们需要对执行的事件进行处理所以接下来我们需要封装一下addOne函数。
// debounce.js
let addBtn=document.getElementById('add')
function addOne(){
console.log('增加一个')
}
function debounce(fun){
return function(){
fun()
}
}
addBtn.addEventListener('click',debounce(addOne))
  • 上面我们用闭包处理了一下addOne函数,接下来我们需要添加一个延时器setTimeout来倒计时,当我们点击按钮后过2s再执行。
// debounce.js
function debounce(fun,time){
return function(){
setTimeout(()=>{
fun()
},time)
}
}
addBtn.addEventListener('click',debounce(addOne,2000))
  • 现在延时的目的是达到了但是每次点击都会新增一个新的setTimeout而且并不能达到我们多次点击只执行一次的效果。
  • 这时候就需要clearTimeout登场了,我们需要在我们点击了按钮后也就是debounce执行时要先把之前的setTimeout先清除再重新计时。
function debounce(fun,time){
 let timer
 return function(){
   clearTimeout(timer)
   timer=setTimeout(()=>{
     fun()
  },time)
}
}

function ajax(content) {
  console.log('ajax request ' )
  console.log(this)
}
 
let inputa = document.getElementById('a')
let inputb = document.getElementById('b')
inputa.onclick = debounce(ajax, 2000)//this->window
inputb.onclick = debounce(ajax, 2000)//this->window
  • 一个防抖功能就完成了,但是这还没完,如果我们在addOne()打印this会发现我们这样执行的this是指向Window的。
  • 这当然不是我们所希望的,我们需要使用apply来改变this指向,再者就是我们需要考虑到执行函数的参数,因为不同的函数肯定会有不同的参数传入,对于参数我们可以使用arguments处理。
  • 上例体现出闭包的作用:a,b都是闭包。它们共享相同的函数定义,但是保存了不同的词法环境。使得a,b两个按钮相互间不影响,点击a清理的是a的词法环境中的timer,b亦同
function debounce(fun,time){
 let timer

// 返回一个闭包函数,用闭包保存timer确保其不会销毁,重复点击会清理上一次的定时器
 return function(){
   clearTimeout(timer)

// 保存事件参数,防止fn函数需要事件参数里的数据
   let args = arguments
   timer=setTimeout(()=>{
     fun.apply(this,args)
  },time)
}
}

function ajax(content) {
  console.log('ajax request ' )
  console.log(this)
}
 
let inputa = document.getElementById('a')
let inputb = document.getElementById('b')
inputa.onclick = debounce(ajax, 2000)//this->a
inputb.onclick = debounce(ajax, 2000)//this->b

这样我们的防抖函数就手写完成了,步骤如下:

  • 声名防抖函数并用闭包处理addOne函数
  • 添加延时器setTimeoutclearTimeout
  • 使用apply来改变this指向,再者就是我们需要考虑到执行函数的参数

函数节流(throttle)

限制一个函数在一段时间内只能执行一次,过了这段时间,在下一段时间又可以执行一次.

具体步骤:

  • 封装一个节流函数,跟防抖一样我们也需要利用闭包,顺便再加一个参数接收节流时间。
  • 节流是在一段时间内执行一次也就是说如果两次鼠标滚动的时间间隔未到所设置的时间则不执行
  • 改变this指向和接收参数,最后完成后是这样的。
function throttle(fun, delay = 1000) {
// 记录第一次的调用时间
var prev = null;
console.log(prev);
// 返回闭包函数
return function () {
// 保存事件参数
var args = arguments;
// 记录现在调用的时间
var now = Date.now();
// console.log(now);
// 如果间隔时间大于等于设置的节流时间
if (now - prev >= delay) {
// 执行函数
fn.apply(this, args);
// 将现在的时间设置为上一次执行时间
prev = now;
}
}
}

总结

对于防抖节流一个最主观的判断方法就是:在10s内你疯狂点击一个按钮,如果使用了防抖则会只执行一次,而你使用了节流则会每隔一段时间执行一次

结合应用场景

  • debounce
    • search搜索联想,用户在不断输入值时,用防抖来节约请求资源。
    • window触发resize的时候,不断的调整浏览器窗口大小会不断的触发这个事件,用防抖来让其只触发一次
  • throttle
    • 鼠标不断点击触发,mousedown(单位时间内只触发一次)
    • 监听滚动事件,比如是否滑到底部自动加载更多,用throttle来判断

 

阅读剩余
THE END