函数防抖与函数节流

only_lzw 7年前
   <h2>区别</h2>    <p>debounce(防抖):当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用此动作则将重新计算执行时间。比如:如果用手指一直按住一个弹簧,它将不会弹起直到你松手为止。</p>    <p>throttle(节流):预先设定一个执行周期,当调用动作的时刻大于等于执行周期则执行该动作,然后进入下一个新周期。比如:将水龙头拧紧直到水是以水滴的形式流出,那你会发现每隔一段时间,就会有一滴水流出。</p>    <h2>适用情景</h2>    <ul>     <li> <p>window对象的resize、scroll事件</p> </li>     <li> <p>拖拽时的mousemove事件</p> </li>     <li> <p>射击游戏中的mousedown、keydown事件</p> </li>     <li> <p>文字输入、自动完成的keyup事件</p> </li>    </ul>    <p>实际上对于window的resize事件,实际需求大多为停止改变大小n毫秒后执行后续处理 (防抖);而其他事件大多的需求是以一定的频率执行后续处理(节流)。</p>    <p>增加一个辅助函数 restArgs</p>    <pre>  <code class="language-javascript">/**       * 类ES6 rest参数的实现,使某个函数具备支持rest参数的能力       * @param func 需要rest参数的函数       * @param startIndex 从哪里开始标识rest参数, 如果不传递, 默认最后一个参数为rest参数       * @returns {Function} 返回一个具有rest参数的函数       */      var restArgs = function (func, startIndex) {          // rest参数从哪里开始,如果没有,则默认视函数最后一个参数为rest参数          // 注意, 函数对象的length属性, 揭示了函数的参数个数          /*           ex: function add(a,b) {return a+b;}           console.log(add.length;) // 2           */r          startIndex = startIndex == null ? func.length - 1 : +startIndex;          // 返回一个支持rest参数的函数          return function () {              // 校正参数, 以免出现负值情况              var length = Math.max(arguments.length - startIndex, 0);              // 为rest参数开辟数组存放              var rest = Array(length);              // 假设参数从2个开始: func(a,b,*rest)              // 调用: func(1,2,3,4,5); 实际的调用是:func.call(this, 1,2, [3,4,5]);              for (var index = 0; index < length; index++) {                  rest[index] = arguments[index + startIndex];              }              // 根据rest参数不同, 分情况调用函数, 需要注意的是, rest参数总是最后一个参数, 否则会有歧义              switch (startIndex) {                  case 0:                      // call的参数一个个传                      return func.call(this, rest);                  case 1:                      return func.call(this, arguments[0], rest);                  case 2:                      return func.call(this, arguments[0], arguments[1], rest);              }              // 如果不是上面三种情况, 而是更通用的(应该是作者写着写着发现这个switch case可能越写越长, 就用了apply)              var args = Array(startIndex + 1);              // 先拿到前面参数              for (index = 0; index < startIndex; index++) {                  args[index] = arguments[index];              }              // 拼接上剩余参数              args[startIndex] = rest;              return func.apply(this, args);          };      };</code></pre>    <h2>debounce</h2>    <p>返回 function 函数的防反跳版本, 将延迟函数的执行(真正的执行)在函数最后一次调用时刻的 wait 毫秒之后. 对于必须在一些输入(多是一些用户操作)停止到达之后执行的行为有帮助。 例如: 渲染一个Markdown格式的评论预览, 当窗口停止改变大小之后重新计算布局, 等等.</p>    <p>传参 immediate 为 true, debounce会在 wait 时间间隔的开始调用这个函数 。在类似不小心点了提交按钮两下而提交了两次的情况下很有用。</p>    <pre>  <code class="language-javascript">var debounce = function (func, wait, immediate) {          var timeout, result;            var later = function (context, args) {              timeout = null;              if (args) result = func.apply(context, args);          };            var debounced = restArgs(function (args) {              // 一旦存在timeout, 意味之前尝试调用过func              // 由于debounce只认最新的一次调用, 所以之前等待执行的func都会被终止              if (timeout) clearTimeout(timeout);              // 如果允许新的调用尝试立即执行,              if (immediate) {                  // 如果之前尚没有调用尝试,那么此次调用可以立马执行,否则一定得等待之前的执行完毕                  var callNow = !timeout;                  // 刷新timeout                  timeout = setTimeout(later, wait);                  // 如果能被立即执行,立即执行                  if (callNow) result = func.apply(this, args);              } else {                  // 否则,这次尝试调用会延时wait个时间                  timeout = delay(later, wait, this, args);              }                return result;          });            debounced.cancel = function () {              clearTimeout(timeout);              timeout = null;          };            return debounced;      };</code></pre>    <h2>throttle</h2>    <p>创建并返回一个像节流阀一样的函数,当重复调用函数的时候,至少每隔 wait毫秒调用一次该函数。对于想控制一些触发频率较高的事件有帮助。(愚人码头注:详见:javascript函数的throttle和debounce,感谢 @澳利澳先生 的翻译建议)</p>    <p>默认情况下,throttle将在你调用的第一时间尽快执行这个function,并且,如果你在wait周期内调用任意次数的函数,都将尽快的被覆盖。如果你想禁用第一次首先执行的话,传递{leading: false},还有如果你想禁用最后一次执行的话,传递{trailing: false}。</p>    <pre>  <code class="language-javascript">var throttle = function (func, wait, options) {            var timeout, context, args, result;          // 最近一次func被调用的时间点          var previous = 0;          if (!options) options = {};            // 创建一个延后执行的函数包裹住func的执行过程          var later = function () {              // 执行时,刷新最近一次调用时间              previous = options.leading === false ? 0 : new Date();              // 清空定时器              timeout = null;              result = func.apply(context, args);              if (!timeout) context = args = null;          };            // 返回一个throttled的函数          var throttled = function () {              // ----- 节流函数开始执行----              // 我们尝试调用func时,会首先记录当前时间戳              var now = new Date();              // 是否是第一次调用              if (!previous && options.leading === false) previous = now;              // func还要等待多久才能被调用 =  预设的最小等待期-(当前时间-上一次调用的时间)              // 显然,如果第一次调用,且未设置options.leading = false,那么remaing=0,func会被立即执行              var remaining = wait - (now - previous);              // 记录之后执行时需要的上下文和参数              context = this;              args = arguments;                // 如果计算后能被立即执行              if (remaining <= 0 || remaining > wait) {                  // 清除之前的“最新调用”                  if (timeout) {                      clearTimeout(timeout);                      timeout = null;                  }                  // 刷新最近一次func调用的时间点                  previous = now;                  // 执行func调用                  result = func.apply(context, args);                  // 如果timeout被清空了,                  if (!timeout) context = args = null;                } else if (!timeout && options.trailing !== false) {                  // 如果设置了trailing edge,那么暂缓此次调用尝试的执行                  timeout = setTimeout(later, remaining);              }              return result;          };            // 可以取消函数的节流化          throttled.cancel = function () {              clearTimeout(timeout);              previous = 0;              timeout = context = args = null;          };            return throttled;      };</code></pre>    <p>参考文章</p>    <p><a href="/misc/goto?guid=4959736379643736404" rel="nofollow,noindex">http://www.tuicool.com/articl...</a></p>    <p><a href="/misc/goto?guid=4959736379724641790" rel="nofollow,noindex">http://blog.csdn.net/jinboker...</a></p>    <p><a href="/misc/goto?guid=4959736379807972576" rel="nofollow,noindex">http://www.css88.com/doc/unde...</a></p>    <p> </p>    <p>来自:https://segmentfault.com/a/1190000008275548</p>    <p> </p>