理解 Debouncing 与 Throttling 的区别

debouncethrottle 是前端开发中经常使用到的高阶函数,都是用来处理 Timing Issues 的,两者作用看似相同,都是为了防止函数被高频调用,但实际内部还是有很大差异的。

为什么要引入这两个高阶函数呢?我们可以设想一下的情景:
很多 app 中都有搜索框,而一般搜索框都会配备智能联想的功能,例如输入一个关键词的拼音可以联想出相关的完整关键词,但为了减轻服务器压力,减少用户不必要的流量开销,我们需要一种机制来限制 API 请求的频率。这就是引入这两种高阶函数的原因。

两种节流函数的区别

Throttle (节流阀)

首先我们来看看 throttle 函数的工作方式,从字面意思上看可以理解为事件在一个管道中传输,加上这个节流阀以后,事件的流速就会减慢。实际上这个函数的作用就是如此,它可以将一个函数的调用频率限制在一定阈值内,例如 1s,那么 1s 内这个函数一定不会被调用两次,这里我画了一个形象的示意图,如下:

上方的时间轴代表上游事件,可能是用户的输入事件或设备传感器发出的回调事件,如果没有经过 throttle 函数处理,那么每次事件就会对应一次响应,假设一个用户某次输入了 10 个字符的搜索关键字,那么服务器就需要处理 10 次检索请求,而如果加上节流阀,并且用户输入文字的手速很快,那么可能服务器就会收到两次请求。

下面我用 Swift 做了一个简易实现:

func throttle(threshold: TimeInterval, action: @escaping Action) -> Action {
    var last: CFAbsoluteTime = 0
    var timer: DispatchSourceTimer?
    return {
        let current = CFAbsoluteTimeGetCurrent();
        if current >= last + threshold {
            action()
            last = current
        } else {
            if timer != nil {
                timer!.cancel()
            }
            
            timer = DispatchSource.makeTimerSource()
            timer!.setEventHandler {
                action()
            }
            
            timer!.scheduleOneshot(deadline: .now() + .milliseconds(Int(threshold * 1000)))
            timer!.activate()
        }
    }
}

实际上就是记录每次函数被实际调用时的绝对时间,如果下次调用时没有到达指定时间,就推迟这次调用。这里要注意的是不能直接忽略掉这次调用,因为有可能会忽略掉用户最后一次输入操作而导致最终结果不完整,因此我们设置了一个 **Timer **来延迟触发,并且如果有新 timer 要启动,首先取消掉旧 timer,因为那次调用的结果已经没有意义了。

然后我们看一下实际的效果(不动请在新窗口打开):


可以看到,不管我点击按钮有多快,在指定时间内只能触发这么多次事件,而设置多长时间的阈值就视服务器性能和带宽等因素而定了。

Debounce (防抖动)

在说明这个函数之前,我想举一个大家都肯定遇见过的例子,那就是鼠标连击。鼠标的微动开关都是有寿命的,而寿命长短与质量都参差不齐,很多廉价鼠标经常会出现连击的问题,就是把单击当成双击(或者三击甚至更多击...)。如果系统的鼠标事件被 debounce 函数处理过,那么这个问题就不可能发生了。事实上不管是 Windows 还是 macOS 都有相应的钩子 API 来做到这件事,有兴趣大家可以自己写一个小程序来应对鼠标连击。

那么这个函数到底做了什么事呢?我们先看下面这张示意图:


我们可以看到,不管上游事件触发了多少次,下游就产生了一次事件。也就是说当一次事件发生后,事件处理器要等一定阈值的时间,如果这段时间过去后 再也没有 事件发生,就处理最后一次发生的事件。假设还差 0.01 秒就到达指定时间,这时又来了一个事件,那么之前的等待作废,需要重新再等待指定时间。

实现如下:

func debounce(threshold: TimeInterval, action: @escaping Action) -> Action {
    var timer: DispatchSourceTimer?
    return {
        if timer != nil {
            timer!.cancel()
        }
        
        timer = DispatchSource.makeTimerSource()
        timer!.setEventHandler {
            action()
        }
        
        timer!.scheduleOneshot(deadline: .now() + .milliseconds(Int(threshold * 1000)))
        timer!.activate()
    }
}

同样是使用了 timer,每次函数被调用时都开启一个 timer 来推迟内部函数的执行,同时取消旧的 timer。这个函数实现起来比较简单。

下面是实际效果(不动请在新窗口打开):


如果这个函数被应用到搜索中去,最终的效果就是,每次用户输入完他想搜索的关键字后,API 才会被调用,不管中途他输入了多少字,输入了多长时间。

总结

两种函数在实际应用中选择哪一个归根到底还要看使用场景,对于其实现,我们需要讲究一个原则就是:最后一次事件一定要得到处理。文中的代码实现可以运用到生产环境,但并不是 thread-safe 的,只适合 UI 层的事件处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,829评论 1 331
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,603评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,846评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,600评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,780评论 3 272
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,695评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,136评论 2 293
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,862评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,453评论 0 229
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,942评论 2 233
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,347评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,790评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,293评论 3 221
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,839评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,448评论 0 181
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,564评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,623评论 2 249

推荐阅读更多精彩内容

  • 1 概念 首先讲讲关注函数节流这个概念的原因~~,其实还是因为业务驱动,在实现无限加载组件(就是滚动到底部,就可以...
    Coder_不易阅读 2,336评论 1 3
  • 最近在研究页面渲染及web动画的性能问题,以及拜读《CSS SECRET》(CSS揭秘)这本大作。 本文主要想谈谈...
    108N8阅读 1,057评论 0 11
  • 一场雨,一场梦,一世的繁华如星落,徒留伊人,轻叹息,空惆怅 亿往昔,雨无香,一柄纸伞随风立,濡湿了唇角的羞涩,落入...
    陌上草薰1228阅读 172评论 0 0
  • 对这个世界了解越深 你就会越明白自己的 浅薄和无知 空着的木桶滚得最响 静心修炼 不负时光
    小史努比阅读 305评论 0 0