js 防抖和节流的实现

319 阅读3分钟

防抖(debounce)

  什么是防抖?

    任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

    underscore 库通过 _.debounce(function, wait, [immediate]) 实现防抖,返回 function 函数的防抖版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后.

  依照 underscore 实现防抖   

节流(throttle)

    什么是节流?

    指定时间间隔内只会执行一次任务。控制一些触发频率较高的事件。

    在开发的时候我们经常遇到一些频繁的事件触发,比如:resize、scroll、mousemove、keyup、keydown

    underScore 库通过 _.throttle(function, wait, [options]) 实现节流,创建并返回一个像节流阀一样的函数,当重复调用函数的时候,至少每隔 wait毫秒调用一次该函数。默认情况下,throttle将在你调用的第一时间尽快执行这个function,并且,如果你在wait周期内调用任意次数的函数,都将尽快的被覆盖。如果你想禁用第一次首先执行的话,传递{leading: false}, 还有如果你想禁用最后一次执行的话,传递{trailing: false}。

  依照undersore实现节流

    1.通过时间戳的形式实现

const throttle = function(fn, wait) {  let lastTime = 0;  return function() {      const nowTime = +new Date();      const context = this;      const args = arguments;      if (nowTime - lastTime > wait) {          fn.apply(context, args);          lastTime = nowTime;      }  }};


   在react项目中使用,在鼠标一进入区域执行事件,鼠标在3秒多移出,这个时候便不再执行。


    当我们需要在界面中显示当前触发的次数,在handleMouseMove 的时候 setState, 发现函数并没有起到节流的效果,由于触发事件会立即执行,所以立即 setState() 之后会重新 render(), 重新事件触发, 事件又会立即执行。

2. 通过使用setTimeout定时器

    当事件触发时,设置一个定时器,当再次触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。

const throttle = function(fn, wait) { 

   let timer = null; 

   return function() { 

     const context = this; 

     const args = arguments;

     if (!timer) { 

       timer = setTimeout(() => { 

        timer = null; fn.apply(context, args); 

       }, wait); 

     } 

   }

}

    当鼠标移入区域的时候不会立即执行,需要等到一秒之后才执行,但是在3秒多的时候鼠标移出区域,依然执行了一次事件。

     所以使用时间戳的写法在事件会立即执行,但是事件停止触发后便不再执行事件,使用第二种定时器的方式,事件不会立即执行,在事件停止触发之后还会再执行一次。

     但是,如果我们想要事件在一开始触发,并且事件停止触发之后再执行一次

const throttle = function(fn ,wait) {  let lastTime = 0, timer = null;  return function() {    const now = +new Date();    const context = this;    const args = arguments;    if (now - lastTime > wait) {      if (timer) {        clearTimeout(timer);        timer = null;      }      fn.apply(context, args);      lastTime = now;          } else if (!timer) {      timer = setTimeout(() => {        timer = null;        lastTime = +new Date();        fn.apply(context, args);      }, wait)    }  }}

   但是有时,我们希望有个设置去指定我是否需要在一开始触发事件或者事件停止事件之后再触发。所以传入一个配置, 通过 leading: false 来禁止刚开始就执行事件;trailing:false 来禁用最后一次执行。

const throttle = function(fn ,wait, option = {}) {  let lastTime = 0, timer = null;  const { leading, trailing } = option;  return function() {    const now = +new Date();    if (!lastTime && !leading) lastTime = now;    const context = this;    const args = arguments;    if (now - lastTime > wait) {      if (timer) {        clearTimeout(timer);        timer = null;      }      fn.apply(context, args);      lastTime = now;    } else if (!timer && !trailing) {      timer = setTimeout(() => {        timer = null;        lastTime = +new Date();        fn.apply(context, args);      }, wait)    }  }}