防抖、节流函数的简单实现

130 阅读4分钟

防抖函数

什么是防抖函数

我们用一幅图来理解他的过程

image.png

  • 当事件触发的时候,相应的函数并不会立即触发,而是等待一定的时间
  • 当事件密集触发的时候,函数的触发也会频繁的推迟
  • 只有等待了一段时间也没有事件触发,才会真正执行响应函数

什么是节流函数

同样,用一幅图来理解他的过程

image.png

  • 当事件触发时,会执行这个响应函数
  • 如果这个事件函数频繁触发,节流函数会按照一定的频率来执行函数
  • 不管在这个中间有多少次触发事件,执行函数总会按照一定的频率执行

防抖函数的简单实现

基本实现


function debounce(fn,delay){
    // 定义一个定时器变量,保存上一次的定时器
    let timer = null
    
    // 真正的执行函数
    return function _debounce(){
        //如果上一次定时器存在,取消上一次的定时器
        if(timer) clearTimeout(timer)
        
        // 延时执行事件函数
        timer = setTimeout(()=>{
            //外部传入的真正要执行的函数
            fn()
        },delay)
     }
}     

优化一:优化参数和this的指向

function debounce(fn,delay){
    // 定义一个定时器变量,保存上一次的定时器
    let timer = null
    
    // 真正的执行函数
    return function _debounce(...args){
        //如果上一次定时器存在,取消上一次的定时器
        if(timer) clearTimeout(timer)
        
        // 延时执行事件函数
        timer = setTimeout(()=>{
            //外部传入的真正要执行的函数
            fn.apply(this,args)
        },delay)
     }
} 

优化二:立即执行

function debounce(fn,delay,immediate =false){
    // 定义一个定时器变量,保存上一次的定时器
    let timer = null
    //
    let isInvoke = false
    
    // 真正的执行函数
    return function _debounce(...args){
        //如果上一次定时器存在,取消上一次的定时器
        if(timer) clearTimeout(timer)
        
        //判断是否立即执行 
        if(immediate && !isInvoke){
            fn.apply(this,args)
            isInvoke = true
        }else{    
            // 延时执行事件函数
            timer = setTimeout(()=>{
                //外部传入的真正要执行的函数
                fn.apply(this,args)
                isInvoke = false
            },delay)
         }   
     }
}

优化三:取消功能

function debounce(fn,delay,immediate =false){
    // 定义一个定时器变量,保存上一次的定时器
    let timer = null
    //
    let isInvoke = false
    
    // 真正的执行函数
    const _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)
         }   
     }
     //封装取消功能
     _debounce.cancel =function(){
         if(timer) clearTimeout(timer)
         timer = null
         isInvoke = false
      }
      
      return _debounce 
}

节流函数的简单实现

基本实现

function throttle(fn,interval){
    //记录上一次开始的时间
    let lastTime = 0
    
    //事件触发时,真正执行的函数
    const _throttle = function(){
        //获取当前事件的触发时间
        const nowTime = new Date().getTime()
        
        //使用当前触发时间和之前的时间间隔以及上一次开始的时间,计算出还剩余多长时间去触发函数
        const remainTime =  interval - (nowTime - lastTime)
        
        if(remainTime <= 0){
            //真正的触发函数
            fn()
            //保留上次的触发事件
            lastTime = nowTime
         }
      }
      return _throttle
}

优化一:决定第一次触不触发

/**
@param leading 用于控制第一次触不触发
*/
function throttle(fn,interval,options={leading:true}){
    //记录上一次开始的时间
    let lastTime = 0
    const { leading } = options
    
    //事件触发时,真正执行的函数
    const _throttle = function(){
        //获取当前事件的触发时间
        const nowTime = new Date().getTime()
        if(!lastTime && !leading) lastTime = nowTime
        
        //使用当前触发时间和之前的时间间隔以及上一次开始的时间,计算出还剩余多长时间去触发函数
        const remainTime =  interval - (nowTime - lastTime)
        
        if(remainTime <= 0){
            //真正的触发函数
            fn()
            //保留上次的触发事件
            lastTime = nowTime
         }
      }
      return _throttle
}

优化二:超过频率的最后一次执不执行

/**
* @param leading 用于控制第一次触不触发
* @param trailing 控制超过频率的最后一次执不执行
*/
function throttle(fn,interval,options={leading:true,trailing: false}){
    //记录上一次开始的时间
    let lastTime = 0
    const { leading ,trailing } = options
    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(trailing && !timer){
             timer = setTimeout(()=>{
                 timer = null
                 lastTime = !leading ? 0 : new Date().getTime()
                 fn()
              },remainTime)   
      }
      return _throttle
}

优化三:优化参数和this的指向

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

  // 2.事件触发时, 真正执行的函数
  const _throttle = function(...args) {

    // 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.真正触发函数
      fn.apply(this, args)
      // 2.4.保留上次触发的时间
      lastTime = nowTime
      return
    }

    if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null
        lastTime = !leading ? 0: new Date().getTime()
        fn.apply(this, args)
      }, remainTime)
    }
  }

  _throttle.cancel = function() {
    if(timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }

  return _throttle
}

优化四:取消功能

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

  // 2.事件触发时, 真正执行的函数
  const _throttle = function(...args) {

    // 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.真正触发函数
      fn.apply(this, args)
      // 2.4.保留上次触发的时间
      lastTime = nowTime
      return
    }

    if (trailing && !timer) {
      timer = setTimeout(() => {
        timer = null
        lastTime = !leading ? 0: new Date().getTime()
        fn.apply(this, args)
      }, remainTime)
    }
  }

  _throttle.cancel = function() {
    if(timer) clearTimeout(timer)
    timer = null
    lastTime = 0
  }

  return _throttle
}