html5中文学习网

您的位置: 首页 > 网络编程 > 其它综合 » 正文

Javascript 的 Debounce 和 Throttle 的原理及实现 · Issue #7 · lishengzxc/bblog-

[ ] 已经帮助:人解决问题

分析_.debounce.throttle

DOM 上有些事件是会频繁触发的,比如mouseoverscrollresize…。以前有个需求,是做一个图表,是用canvas画的,最初,如果图表画完,用户拖拽浏览器窗口,改变浏览器大小的话,图表并不会自适应的变化,所以就需要监听resize事件,每当窗口大小变化后,再重新绘制。但是resize是频繁触发的,这就导致了页面的明显的卡顿,因为每次resize后的回调要执行大量的计算。zU4HTML5中文学习网 - HTML5先行者学习网

当时比较急,遇到这个问题以后,直接就查了.debounce.throttle,就直接用了lodash。现在回过头了,看下源码,看看它的实现。zU4HTML5中文学习网 - HTML5先行者学习网

Debounce

英文释义:

n. 防反跳zU4HTML5中文学习网 - HTML5先行者学习网

按键防反跳(Debounce)为什么要去抖动呢?机械按键在按下时,并非按下就接触的很好,尤其是有簧片的机械开关,会在接触的瞬间反复的开合多次,直到开关状态完全改变。zU4HTML5中文学习网 - HTML5先行者学习网

我们希望开关只捕获到那次最后的精准的状态切换。在 Javascript 中,那些 DOM 频繁触发的事件,我们想在某个时间点上去执行我们的回调,而不是每次事件每次触发,我们就执行该回调。有点啰嗦,再简单一点的说,我们希望多次触发的相同事件的触发合并为一次触发(其实还是触发了好多次,只是我们只关注那一次)。zU4HTML5中文学习网 - HTML5先行者学习网

所以,在 Javascript 中,我们就希望频繁事件的回调函数在某段连续时间内,在事件触发后只执行一次zU4HTML5中文学习网 - HTML5先行者学习网

resize事件为例,在 2s 内的该事件会被触发多次(具体几次未知,不同浏览器并不一样)。我们需要对resize的回调函数做 Debounce 100ms 化,这样resize的回调会在 2.1s 后触发,之前 2s 以内的resize我就无视了。zU4HTML5中文学习网 - HTML5先行者学习网

我们先自己实现一个

/** * * @param fn {Function}   实际要执行的函数 * @param delay {Number}  延迟时间,单位是毫秒(ms) * * @return {Function}     返回一个“防反跳”了的函数 */function debounce(fn, delay) {  // 定时器,用来 setTimeout  var timer  // 返回一个函数,这个函数会在一个时间区间结束后的 delay 毫秒时执行 fn 函数  return function () {    // 保存函数调用时的上下文和参数,传递给 fn    var context = this    var args = arguments    // 每次这个返回的函数被调用,就清除定时器,以保证不执行 fn    clearTimeout(timer)    // 当返回的函数被最后一次调用后(也就是用户停止了某个连续的操作),    // 再过 delay 毫秒就执行 fn    timer = setTimeout(function () {      fn.apply(context, args)    }, delay)  }}

将 Debounce 化后的闭包函数作为频繁触发的事件的回调,其实还是频繁执行的,只不过,返回的闭包函数内部通过clearTimeout(),让真正需要执行的回调函数不执行了,只有在连续时间内,不在触发频繁事件后的delay秒后,执行真正的回调。zU4HTML5中文学习网 - HTML5先行者学习网

Demo

http://lishengzxc.github.io/bblog/Debounce.htmlzU4HTML5中文学习网 - HTML5先行者学习网

document.addEventListener('mousemove', debounce(() => console.log(new Date().getTime()), 1000), false);

再鼠标不移动后的 1000ms 后,执行了真正的回调,打印了当前时间戳。zU4HTML5中文学习网 - HTML5先行者学习网

比较合理的应用场景还有,在一个表单的输入框中(包括多行文本输入框),想当用户停止输入后,再ajaxzU4HTML5中文学习网 - HTML5先行者学习网

input.addEventListener('keyup', debounce(() => ajax(...), 1000), false);

_.debounce()分析

先看看underscore的。zU4HTML5中文学习网 - HTML5先行者学习网

  _.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) {      if (timeout) clearTimeout(timeout);      if (immediate) {        var callNow = !timeout;        timeout = setTimeout(later, wait);        if (callNow) result = func.apply(this, args);      } else {        timeout = _.delay(later, wait, this, args);      }      return result;    });    debounced.cancel = function() {      clearTimeout(timeout);      timeout = null;    };    return debounced;  };

它的debounce还接受第三个参数immediate,这个参数是用来配置回调函数是在一个时间区间的最开始执行(immediatetrue),还是最后执行(immediatefalse),如果immediatetrue,意味着是一个同步的回调,可以传递返回值。zU4HTML5中文学习网 - HTML5先行者学习网

关键的地方是,单独拿出了一个later函数通过控制timer来觉得连续的时间除一开始后,是不是要执行回调。zU4HTML5中文学习网 - HTML5先行者学习网

loadshdebounce,接受更多的配置:zU4HTML5中文学习网 - HTML5先行者学习网

  • [options.leading=false] (boolean): Specify invoking on the leading edge of the timeout.
  • [options.maxWait] (number): The maximum time func is allowed to be delayed before it’s invoked.
  • [options.trailing=true] (boolean): Specify invoking on the trailing edge of the timeout.

lodashdebounce源代码zU4HTML5中文学习网 - HTML5先行者学习网

leading=true等效于underscoreimmediate=truetrailing则正好相反。maxWait设置了超时时间,在规定的超时间后,一定调用回调。(通过内部设置了两个setTimeout,一个用来完成基础功能,让回调只执行一次,还有一个用来控制超时)zU4HTML5中文学习网 - HTML5先行者学习网

Throttle

英文释义:

n. 节流阀zU4HTML5中文学习网 - HTML5先行者学习网

throttle就是设置固定的函数执行速率,从而降低频繁事件回调的执行次数。前面提到的canvas绘制好的图表后,用户改变窗口大小后,重新绘制图表,就很适合使用throttle。最近在写一个 Tank 游戏,用户可以非常快的点击开火,但是我们需要通过 Throttle,来降低一些开火的频率。zU4HTML5中文学习网 - HTML5先行者学习网

我们先自己实现一个

/**** @param fn {Function}   实际要执行的函数* @param delay {Number}  执行间隔,单位是毫秒(ms)** @return {Function}     返回一个“节流”函数*/function throttle(fn, threshhold) {  // 记录上次执行的时间  var last  // 定时器  var timer  // 默认间隔为 250ms  threshhold || (threshhold = 250)  // 返回的函数,每过 threshhold 毫秒就执行一次 fn 函数  return function () {    // 保存函数调用时的上下文和参数,传递给 fn    var context = this    var args = arguments    var now = +new Date()    // 如果距离上次执行 fn 函数的时间小于 threshhold,那么就放弃    // 执行 fn,并重新计时    if (last && now < last + threshhold) {      clearTimeout(timer)      // 保证在当前时间区间结束后,再执行一次 fn      timer = setTimeout(function () {        last = now        fn.apply(context, args)      }, threshhold)    // 在时间区间的最开始和到达指定间隔的时候执行一次 fn    } else {      last = now      fn.apply(context, args)    }  }}

代码中,比较关键的部分是最后部分的if .. else ..,每次回调执行以后,需要保存执行的函数的时间戳,为了计算以后的事件触发回调时与之前执行回调函数的时间戳的间隔,从而根据间隔判断要不要执行回调。zU4HTML5中文学习网 - HTML5先行者学习网

_.throttle()分析

直接看underscore的。因为lodash没有对其再进行包装。zU4HTML5中文学习网 - HTML5先行者学习网

 _.throttle = function(func, wait, options) {    var timeout, context, args, result;    var previous = 0;    if (!options) options = {};    var later = function() {      previous = options.leading === false ? 0 : _.now();      timeout = null;      result = func.apply(context, args);      if (!timeout) context = args = null;    };    var throttled = function() {      var now = _.now();      if (!previous && options.leading === false) previous = now;      var remaining = wait - (now - previous);      context = this;      args = arguments;      if (remaining <= 0 || remaining > wait) {        if (timeout) {          clearTimeout(timeout);          timeout = null;        }        previous = now;        result = func.apply(context, args);        if (!timeout) context = args = null;      } else if (!timeout && options.trailing !== false) {        timeout = setTimeout(later, remaining);      }      return result;    };    throttled.cancel = function() {      clearTimeout(timeout);      previous = 0;      timeout = context = args = null;    };    return throttled;  };

其中previous相当于自己实现代码中的last。它还接受leadingtrailing来控制真正回调触发的时机,这和lodash_.debounce 差不太多。zU4HTML5中文学习网 - HTML5先行者学习网

最后

这里有一个可视化分析页面:http://demo.nimius.net/debounce_throttle/,大家可以点开看看。zU4HTML5中文学习网 - HTML5先行者学习网

来源: Javascript 的 Debounce 和 Throttle 的原理及实现 · Issue #7 · lishengzxc/bblogzU4HTML5中文学习网 - HTML5先行者学习网

(责任编辑:)
推荐书籍
推荐资讯
关于HTML5先行者 - 联系我们 - 广告服务 - 友情链接 - 网站地图 - 版权声明 - 人才招聘 - 帮助