在处理诸如 resizescrollmousemove 和 keydown/keyup/keypress 等事件的时候,通常我们不希望这些事件太过频繁地触发,尤其是监听程序中涉及到大量的计算或者有非常耗费资源的操作。

有多频繁呢?以 mousemove 为例,根据 DOM Level 3 的规定,「如果鼠标连续移动,那么浏览器就应该触发多个连续的 mousemove 事件」,这意味着浏览器会在其内部计时器允许的情况下,根据用户移动鼠标的速度来触发 mousemove 事件。(当然了,如果移动鼠标的速度足够快,比如“刷”一下扫过去,浏览器是不会触发这个事件的)。resizescroll 和 key* 等事件与此类似。

Debounce

DOM 事件里的 debounce 概念其实是从机械开关和继电器的“去弹跳”(debounce)衍生 出来的,基本思路就是把多个信号合并为一个信号。这篇文章 解释得非常清楚,感兴趣的可以一读。

在 JavaScript 中,debounce 函数所做的事情就是,强制一个函数在某个连续时间段内只执行一次,哪怕它本来会被调用多次。我们希望在用户停止某个操作一段时间之后才执行相应的监听函数,而不是在用户操作的过程当中,浏览器触发多少次事件,就执行多少次监听函数。

比如,在某个 3s 的时间段内连续地移动了鼠标,浏览器可能会触发几十(甚至几百)个 mousemove 事件,不使用 debounce 的话,监听函数就要执行这么多次;如果对监听函数使用 100ms 的“去弹跳”,那么浏览器只会执行一次这个监听函数,而且是在第 3.1s 的时候执行的。

现在,我们就来实现一个 debounce 函数。

实现

我们这个 debounce 函数接收两个参数,第一个是要“去弹跳”的回调函数 fn,第二个是延迟的时间 delay

实际上,大部分的完整 debounce 实现还有第三个参数 immediate ,表明回调函数是在一个时间区间的最开始执行(immediate 为 true)还是最后执行(immediate 为 false),比如 underscore 的 _.debounce。本文不考虑这个参数,只考虑最后执行的情况,感兴趣的可以自行研究。

/**
*
* @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 的使用方式如下:其实思路很简单,debounce 返回了一个闭包,这个闭包依然会被连续频繁地调用,但是在闭包内部,却限制了原始函数 fn 的执行,强制 fn 只在连续操作停止后只执行一次。

$(document).on('mouvemove', debounce(function(e) {
// 代码
}, 250))

还是以 mousemove 为例,为其绑定一个“去弹跳”的监听器,效果是怎样的?请看这个 Demo。用例

再来考虑另外一个场景:根据用户的输入实时向服务器发 ajax 请求获取数据。我们知道,浏览器触发 key* 事件也是非常快的,即便是正常人的正常打字速度,key* 事件被触发的频率也是很高的。以这种频率发送请求,一是我们并没有拿到用户的完整输入发送给服务器,二是这种频繁的无用请求实在没有必要。

更合理的处理方式是,在用户“停止”输入一小段时间以后,再发送请求。那么 debounce 就派上用场了:

$('input').on('keyup', debounce(function(e) {
// 发送 ajax 请求
}, 300))

Throttle

throttle 的概念理解起来更容易,就是固定函数执行的速率,即所谓的“节流”。正常情况下,mousemove 的监听函数可能会每 20ms(假设)执行一次,如果设置 200ms 的“节流”,那么它就会每 200ms 执行一次。比如在 1s 的时间段内,正常的监听函数可能会执行 50(1000/20) 次,“节流” 200ms 后则会执行 5(1000/200) 次。

我们先来看 Demo。可以看到,不管鼠标移动的速度是慢是快,“节流”后的监听函数都会“匀速”地每 250ms 执行一次。

实现

与 debounce 类似,我们这个 throttle 也接收两个参数,一个实际要执行的函数 fn,一个执行间隔阈值 threshhold

同样的,throttle 的更完整实现可以参看 underscore 的 _.throttle

/**
*
* @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)
}
}
}

  原理也不复杂,相比 debounce,无非是多了一个时间间隔的判断,其他的逻辑基本一致。throttle 的使用方式如下:

$(document).on('mouvemove', throttle(function(e) {
// 代码
}, 250))

用例

throttle 常用的场景是限制 resize 和 scroll 的触发频率。以 scroll 为例。

总结

debounce 强制函数在某段时间内只执行一次,throttle 强制函数以固定的速率执行。在处理一些高频率触发的 DOM 事件的时候,它们都能极大提高用户体验。

Debounce 和 Throttle【转载】的更多相关文章

  1. 白话debounce和throttle

    遇到的问题 在开发过程中会遇到频率很高的事件或者连续的事件,如果不进行性能的优化,就可能会出现页面卡顿的现象,比如: 鼠标事件:mousemove(拖曳)/mouseover(划过)/mouseWhe ...

  2. Debounce 和 Throttle 的原理及实现---防止频繁触发某事件

    原文:http://blog.csdn.net/redtopic/article/details/69396722 在处理诸如 resize.scroll.mousemove 和 keydown/ke ...

  3. debounce 与 throttle 区别

    原文地址:http://undefinedblog.com/debounce-and-throttle/ 二.什么是debounce    1. 定义 如果用手指一直按住一个弹簧,它将不会弹起直到你松 ...

  4. 函数防抖与函数节流 封装好的debounce和throttle函数

    /** * 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * * @param {function} func 传入函数,最后一个参数是额外增加的this对象,. ...

  5. debounce与throttle区别

    在2011年,Twitter网站曾爆出一个问题:在主页往下滚动时,页面会变得缓慢以致没有响应.John Resig发表了一篇文章< a blog post about the problem&g ...

  6. underscore里面的debounce与throttle

    throttle 策略的电梯.保证如果电梯第一个人进来后,15秒后准时运送一次,不等待.如果没有人,则待机. debounce 策略的电梯.如果电梯里有人进来,等待15秒.如果又人进来,15秒等待重新 ...

  7. debounce、throttle、requestAnimationFrame

    今天review同事代码,代码实现了返回顶部的功能,用到了lodash库中的throttle,我看着眼生,于是乎去看了下lodash文档,然后牵出了debounce,具体的知识点,这里不再赘述,底部的 ...

  8. debounce还是throttle(去抖和节流)

    debounce 去抖 我的理解很简单,比方说window.onscroll会疯狂触发handler,此时给它一个debounce(handler, delayTime). 就是不管你延时时间内触发了 ...

  9. JS魔法堂:函数节流(throttle)与函数去抖(debounce)

    一.前言 以下场景往往由于事件频繁被触发,因而频繁执行DOM操作.资源加载等重行为,导致UI停顿甚至浏览器崩溃. 1. window对象的resize.scroll事件 2. 拖拽时的mousemov ...

随机推荐

  1. BNUOJ 34990 Justice String

    Justice String Time Limit: 2000ms Memory Limit: 65536KB 64-bit integer IO format: %lld      Java cla ...

  2. [MST] Derive Information from Models Using Views

    Redundant data or caching data is a constant source of bugs. MST adheres to the philosophy that no d ...

  3. Unity3d 开发(七)AssetBundle组织文件夹

    本文探讨怎样配置一个AssetBundle更为合理. 对于结构为 的文件夹结构,当中shared是Hero文件夹下须要用到的公用资源.即公有依赖.可採用例如以下的打包策略 整个文件夹打包 将整个100 ...

  4. 主程的晋升攻略(4):TCP、消息分包和协议设计

    在<主程的晋升攻略(3):IP.DNS和CDN>中,一次网络请求经过DNS解析知道了目的IP,如今就要发出网络包,这里我们说一说TCP的相关话题. TCP是一种流式协议 讲网络编程的教科书 ...

  5. 覆盖率測试工具gcov的前端工具_LCOV_简单介绍

    1.Gcov是进行代码运行的覆盖率统计的工具.它随着gcc的公布一起公布的,它的使用也非常easy,须要在编译和链接的时候加上-fprofile-arcs -ftest-coverage生成二进制文件 ...

  6. node03--http

    form.html <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...

  7. nyoj--42--一笔画问题(并查集)

    一笔画问题 时间限制:3000 ms  |  内存限制:65535 KB 难度:4 描述 zyc从小就比较喜欢玩一些小游戏,其中就包括画一笔画,他想请你帮他写一个程序,判断一个图是否能够用一笔画下来. ...

  8. 数据结构之fhq-treap

    本文内容部分转自某大佬博客:https://blog.csdn.net/CABI_ZGX/article/details/79963427 例题:https://www.luogu.org/probl ...

  9. 那些不兼容 IE11的网站(持续更新)

    此博文用于收集不兼容 IE11 的网站,持续更新,请网站开发者自己认领: 兼容性引起的功能缺陷: v.qq.com (提示未安装 Flash 播放器,这问题我反馈几百年了,还没修复) tv.sohu. ...

  10. studyLink

    http://order.csdn.net/myorder/detail?id=850343 csdn