手写防抖与节流函数

77 阅读5分钟

手写防抖debounce函数

当事件触发时,相应的函数并不会立即触发,而是会等待一定的时间

当事件密集触发时,函数的触发会被频繁的推迟

只有等待了一段时间也没有事件触发,才会真正的执行响应函数

防抖的应用场景很多:

输入框中频繁的输入内容,搜索或者提交信息;

频繁的点击按钮,触发某个事件;

监听浏览器滚动事件,完成某些特 定操作;

用户缩放浏览器的resize事件;

大概编写

防抖函数的核心就是 每次函数运行都要等待一定时间才能运行,而当上次运行还没有开始就又来了新的执行函数,就立刻清除上一次的计时器,重新计时。

function debounce(fn,delay){
  //定义一个定时器,保存上一次的定时器
  let timer = null
  //真正的执行函数
  const _debounce = function(){
      if(timer) clearTimeout(timer)

      timer = setTimeout(()=>{
        fn()
      },delay)

  }
  
  return _debounce
}

添加this参数的指向

function debounce(fn,delay){
    let timer = null
    
    const _debounce = function(...args){
        if(timer) clearTimeout(timer)
        timer = setTimeout(()=>{
            fn.apply(this,args)
        },delay);
     }
     return _debounce
}

当第一次执行函数的时候,立即执行

传入第三个参数immediate,初始化为false,表示不立即执行,当传入true后,表示当进行计数器执行一次函数后,下次的函数执行立即执行

function debounce(fn,delay,immediate = false){
    let timer = null
    //标记是否为一个周期内的第一次执行
    let isInvoke = false
    
    cosnt _debounce = function(...args){
        if(timer) clearTimeout(timer)
        
        //判断如果需要立即执行,并且还没有被执行,就直接执行
        if(immediate && !isInvoke){
            fn.apply(this,args)
            isInvoke = true
        }else{
            timer = setTimeout(()=>{
                fn.apply(this,args)
                isInvoke = false
            },delay)
        } 
    }
    return _debounce
}

取消功能

用户可以进行取消


function debounce(fn,delay,immediate = false){
    
    let timer = null
    let isInvoke = false
    
    const _debounce = function(...args){
        if(timer) clearTimeout(timer)
        if(immediate && !isInovke){
            fn.apply(this,args)
            isInvoke = true
        }else{
            timer = setTimout(()=>{
                fn.apply(this,args)
                isInvoke = false
            },delay)
        }
    }   
    _debounce.cancel = function(){
        if(timer)  clearTimeout(timer)
        timer = null
        isInvoke = false
    }
    return _debounce
    
}


函数返回值

function debounce(fn, delay, immediate = false, resultCallback) {
  // 1.定义一个定时器, 保存上一次的定时器
  let timer = null
  let isInvoke = false

  // 2.真正执行的函数
  const _debounce = function(...args) {
    return new Promise((resolve, reject) => {
      // 取消上一次的定时器
      if (timer) clearTimeout(timer)

      // 判断是否需要立即执行
      if (immediate && !isInvoke) {
        const result = fn.apply(this, args)
        if (resultCallback) resultCallback(result)
        resolve(result)
        isInvoke = true
      } else {
        // 延迟执行
        timer = setTimeout(() => {
          // 外部传入的真正要执行的函数
          const result = fn.apply(this, args)
          if (resultCallback) resultCallback(result)
          resolve(result)
          isInvoke = false
          timer = null
        }, delay)
      }
    })
  }

  // 封装取消功能
  _debounce.cancel = function() {
    if (timer) clearTimeout(timer)
    timer = null
    isInvoke = false
  }

  return _debounce
}


手写节流throttle函数

大概编写

function throttle(fn,interval){
    //lastTime 表示上次执行时候的时间
    let lastTime = 0
    
    const _throttle = function(){
        //nowTime 表示当前的时间
        let nowTime = new Date().getTime()
        
        //remainTime 表示在设置的节流间隔内,距离下次执行时间还剩余的时间
        let remainTime = interval - (nowTime - lastTime)
        
        //当剩余时间<=0的时候,表示可以执行,并且更新lastTime到当前时间
        if(remainTime <= 0 ){
            fn()
            lastTime = nowTime
        }
    }
    
    return _throttle
}

添加功能 可以选择是否立即执行

在调用函数的时候,添加一个参数trading,表示在第一次执行函数的时候是否就立刻执行

其实上面的大概编写代码中,已经可以实现 立即执行。因为初始的lastTime为0,但是nowTime为很大的数值,这个时候的remainTime一定小于0,那么在第一时间内就会立刻进行函数的调用。

但是现在要做的事情是,需要加上一个参数,让进行函数调用的时候可以选择是否立即执行

代码如下,其实就是加上一次判断

//初始时候的leading 设置为true
function throttle(fn,interval,leading = true){
    let lastTime = 0
    const _throttle = function(){
    
        //进行是否需要立即执行判断 当lastTime=0表示是第一次,leading为false表示不需要
        if(!lastTime && !leading) lastTime = nowTime
        
        
        let nowTime = new Date().getTime()
        let remainTime = interval - (nowTime - lastTime)
        if(remainTime <= 0 ){
            fn()
        }
    }
    
    return _throttle
}

添加功能 函数最后是否需要调用执行

调用函数的时候传递参数 trading参数,表示是否需要最后一次函数调用执行

核心思想是 需要在最后一次函数调用后 启动一个计时器,计时器的时间就是距离下次要执行的时间间隔

function throttle(fn,interval,options = {leading:true,trading:false}){
    const {leading,trading} = options
    let lastTime = 0
    let timer = null
    const _throttle = function(){
        const nowTime = new Date().getTime()

        if(!lastTime && !leading) lastTime = nowTime
        const remainTime = interval - (nowTime-lastTime)
        if(remainTime <= 0){
            if(timer){
                clearTimeout(timer)
                timer = null
            }
            fn()
            lastTime = nowTime
            
            //已经完成一次执行,就不需要加定时器了
            return
        }

        if(trading && !timer){
            timer =  setTimeout(()=>{
                
                //通过定时器执行完成以后,要将timer置为0防止后面又需要执行
                timer = null
                //对lastTime进行重置的时候,需要注意,如果此时需要进行第一次输出,那就直接为0,
                //否则需要设置为当前时间,从而防止第一次执行
                lastTime = !leading ? 0:new Date().getTime()
                fn()
            },remainTime)
        }
    }
    return _throttle
}

添加取消函数


function throttle(fn, interval, options = { leading: true, trailing: false }) {
  // 1.记录上一次的开始时间
  const { leading, trailing, resultCallback } = options
  let lastTime = 0
  let timer = null

  // 2.事件触发时, 真正执行的函数
  const _throttle = function(...args) {
    return new Promise((resolve, reject) => {
      // 2.1.获取当前事件触发时的时间
      const nowTime = new Date().getTime()
      if (!lastTime && !leading) lastTime = nowTime

      // 2.2.使用当前触发的时间和之前的时间间隔以及上一次开始的时间, 计算出还剩余多长事件需要去触发函数
      const remainTime = interval - (nowTime - lastTime)
      if (remainTime <= 0) {
        if (timer) {
          clearTimeout(timer)
          timer = null
        }

        // 2.3.真正触发函数
        const result = fn.apply(this, args)
        if (resultCallback) resultCallback(result)
        resolve(result)
        // 2.4.保留上次触发的时间
        lastTime = nowTime
        return
      }

      if (trailing && !timer) {
        timer = setTimeout(() => {
          timer = null
          lastTime = !leading ? 0: new Date().getTime()
          const result = fn.apply(this, args)
          if (resultCallback) resultCallback(result)
          resolve(result)
        }, remainTime)
      }
    })
  }
    
  //为_throttle 绑定取消事件
  _throttle.cancel = function() {
    if(timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }

  return _throttle
}