Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js-防抖&节流 #9

Open
ahaow opened this issue May 5, 2022 · 0 comments
Open

js-防抖&节流 #9

ahaow opened this issue May 5, 2022 · 0 comments

Comments

@ahaow
Copy link
Owner

ahaow commented May 5, 2022

debounce

某个函数在某段时间内,无论触发了多少次回调,都只执行最后一次

  • 利用定时器,函数第一次执行时设定一个定时器,之后调用时发现已经设定过定时器就清空之前的定时器,并重新设定一个新的定时器,如果存在没有被清空的定时器,当定时器计时结束后触发函数执行
function debounce(fn, wait) {
  // 缓存一个定时器id
  let timeout = null
  return function(..args) {
    let _self = this
    // 如果已经设定过定时器就清空上一次的定时器
    if (timeout !== null) {
      clearTimeout(timeout)
    }
    timeout = setTimeout(() => {
      fn.apply(_self, args)
    }, wait)
  }
}

实现第一次触发回调事件就执行 fn

function debounce(fn, wait, immediate) {
  // 定义一个时间器id
  let timeout = null
  return function(...args) {
    let _self = this
    // 如果有时间器id就清除
    if (timeout !== null) {
      clearTimeout(timer)
    }
    
    // 实现第一次出发毁掉事件执行
    if (immediate && !timeout) {
      fn.apply(_self, args)
    }
    
    timeout = setTimeout(() => {
      fn.apply(_self, args)
    }, wait)
  }
}

thorttle

节流指的是某个函数在一定时间间隔内(例如 3 秒)执行一次,在这 3 秒内 无视后来产生的函数调用请求

节流适用于函数调用频繁的场景, 例如 window.onreiszemousemove, 上传进度等

实现原理

  • 利用时间戳来判断是否执行时间,记录上次时间戳,每次触发事件回调,判断当前时间戳距离上次时间戳的间隔是否已经达到了时间差, 如果是就执行,并更新上次时间戳,如此循环
  • 利用定时器,设置一个1000s的的定时器,每次触发事件回调,如果定时器存在,则回调方法不执行,直到定时器触发,hander被清除,重新设置定时器 (可以理解成一个防抖debounce)

时间戳来判断

function throttle(fn, wait) {
  let last = 0
  return function(...args) {
    let _self = this
    let now = +new Date()
    if (now - last > wait) {
      last = now
      fn.apply(_self, args)
    }
  }
}

时间戳 + 定时器

function throttle(fn, wait) {
  let timeout = null
  let last = 0
  return function(...args) {
    let _self = this
    let now = +new Date()
    if (last && now - last < wait) {
      if (timeout !== null) {
        clearTimeout(timeout)
      }
      timeout = setTimeout(() => {
        last = now
      	fn.apply(_self, args)
      }, wait)
    } else {
      last = now
      fn.apply(_self, args)
    }
  }
}

L8BaXn.png

undrscore throttle

const throttle = function(func, wait, options) {
  var timeout, context, args, result;
  
  // 上一次执行回调的时间戳
  var previous = 0;
  
  // 无传入参数时,初始化 options 为空对象
  if (!options) options = {};

  var later = function() {
    // 当设置 { leading: false } 时
    // 每次触发回调函数后设置 previous 为 0
    // 不然为当前时间
    previous = options.leading === false ? 0 : _.now();
    
    // 防止内存泄漏,置为 null 便于后面根据 !timeout 设置新的 timeout
    timeout = null;
    
    // 执行函数
    result = func.apply(context, args);
    if (!timeout) context = args = null;
  };

  // 每次触发事件回调都执行这个函数
  // 函数内判断是否执行 func
  // func 才是我们业务层代码想要执行的函数
  var throttled = function() {
    
    // 记录当前时间
    var now = _.now();
    
    // 第一次执行时(此时 previous 为 0,之后为上一次时间戳)
    // 并且设置了 { leading: false }(表示第一次回调不执行)
    // 此时设置 previous 为当前值,表示刚执行过,本次就不执行了
    if (!previous && options.leading === false) previous = now;
    
    // 距离下次触发 func 还需要等待的时间
    var remaining = wait - (now - previous);
    context = this;
    args = arguments;
    
    // 要么是到了间隔时间了,随即触发方法(remaining <= 0)
    // 要么是没有传入 {leading: false},且第一次触发回调,即立即触发
    // 此时 previous 为 0,wait - (now - previous) 也满足 <= 0
    // 之后便会把 previous 值迅速置为 now
    if (remaining <= 0 || remaining > wait) {
      if (timeout) {
        clearTimeout(timeout);
        
        // clearTimeout(timeout) 并不会把 timeout 设为 null
        // 手动设置,便于后续判断
        timeout = null;
      }
      
      // 设置 previous 为当前时间
      previous = now;
      
      // 执行 func 函数
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    } else if (!timeout && options.trailing !== false) {
      // 最后一次需要触发的情况
      // 如果已经存在一个定时器,则不会进入该 if 分支
      // 如果 {trailing: false},即最后一次不需要触发了,也不会进入这个分支
      // 间隔 remaining milliseconds 后触发 later 方法
      timeout = setTimeout(later, remaining);
    }
    return result;
  };

  // 手动取消
  throttled.cancel = function() {
    clearTimeout(timeout);
    previous = 0;
    timeout = context = args = null;
  };

  // 执行 _.throttle 返回 throttled 函数
  return throttled;
};

总结

  • 函数防抖,无论触发多少次,只执行最后一次
  • 函数节流,在一定的时间内,只执行一次,例如 3 秒, 在这 3 秒内无视后来产生的函数调用请求
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant