【vux中的debouce和throttle】Debounce 和 Throttle 的原理及实现

先讲VUX中的用法

1、先引用vux

<div @mousemove="clickdemo" style="width: 400px;height: 400px;background-color: #53fcff;"></div>
<div @mousemove="clickdemo2" style="width: 400px;height: 400px;background-color: #53fcff;"></div>

import {debounce} from 'vux'
import {throttle} from 'vux'

methods:{
     clickdemo: debounce(function () {
                    console.log("测试")
                },
                3000,//.延迟多少毫秒执行
                {
                    leading: true,
                    //maxWait: 1000,
                    trailing: false,
                }),
            /*
            leading,函数在每个等待时延的开始被调用
            trailing,函数在每个等待时延的结束被调用
            maxwait(debounce才有的配置),最大的等待时间,因为如果 debounce 的函数调用时间不满足条件,可能永远都无法触发,因此增加了这个配置,保证大于一段时间后一定能执行一次函数
            */
            
     clickdemo2: throttle(function () {
                    console.log("测试")
                },
                2000,//实行频率
                {
                    leading: true,
                    trailing: false,
                }),
}

据说,debounce中的maxwait如果设置了就相当于是throttle.

debounce应用场景:根据用户的输入实时向服务器发 ajax 请求获取数据。我们知道,浏览器触发 key* 事件也是非常快的,即便是正常人的正常打字速度,key* 事件被触发的频率也是很高的。以这种频率发送请求,一是我们并没有拿到用户的完整输入发送给服务器,二是这种频繁的无用请求实在没有必要,所以可以用debounce,设置trailing:ture,leading:false,让结束一段时间后发送服务器请求。

throttle常用的场景:限制 resize 和 scroll 的触发频率

 

2、什么是throttle和debounce

throttle(又称节流)和debounce(又称防抖)其实都是函数调用频率的控制器,这里只做简单的介绍,如果想了解更多关于这两个定义的细节可以看下后文给出的一张图片,或者阅读一下lodash的文档

throttle:将一个函数的调用频率限制在一定阈值内,例如 1s 内一个函数不能被调用两次。

debounce:当调用函数n秒后,才会执行该动作,若在这n秒内又调用该函数则将取消前一次并重新计算执行时间,举个简单的例子,我们要根据用户输入做suggest,每当用户按下键盘的时候都可以取消前一次,并且只关心最后一次输入的时间就行了。

lodash 对这两个函数又增加了一些参数,主要是以下三个:

  • leading,函数在每个等待时延的开始被调用

  • trailing,函数在每个等待时延的结束被调用

  • maxwait(debounce才有的配置),最大的等待时间,因为如果 debounce 的函数调用时间不满足条件,可能永远都无法触发,因此增加了这个配置,保证大于一段时间后一定能执行一次函数

这里直接剧透一下,其实 throttle 就是设置了 maxwait 的 debounce,所以我这里也只会介绍 debounce 的代码,聪明的读者们可以自己思考一下为什么。

 

3、原理

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

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

可以参看这个 Demo 体会下。

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 返回了一个闭包,这个闭包依然会被连续频繁地调用,但是在闭包内部,却限制了原始函数 fn 的执行,强制 fn 只在连续操作停止后只执行一次。

debounce 的使用方式如下:
用例

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


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

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

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

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

【新】在vue中定义一个debounce

export function debounce(func, delay) {
  let timer

  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    timer = setTimeout(() => {
      func.apply(this, args)
    }, delay)
  }
}

...args是剩余参数语法,将不定参数表示为一个数组,apply()传递两个参数,一个是函数调用的对象,一个是参数数组

 

Throttle

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

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

实现

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

同样的,throttle 的更完整实现可以参看 underscore 的 _.throttle
原理也不复杂,相比 debounce,无非是多了一个时间间隔的判断,其他的逻辑基本一致。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)
    }
  }
}

 

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

用例

throttle 常用的场景是限制 resize 和 scroll 的触发频率。以 scroll 为例,查看这个 Demo 感受下。

可视化解释

如果还是不能完全体会 debounce 和 throttle 的差异,可以到 这个页面 看一下两者可视化的比较。

图片

总结

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vux 实现验证码弹框可以使用其内置的 `popup` 组件结合第三方验证码库实现。以下是大致的实现步骤: 1. 安装第三方验证码库。比如 `geetest`。 2. 在需要弹出验证码的页面引用 `popup` 组件,并在 `data` 定义一个变量来控制弹窗的显示。 ```html <template> <div> <popup v-model="show"> <!-- 验证码弹窗内容 --> </popup> </div> </template> <script> export default { data () { return { show: false } } } </script> ``` 3. 在需要触发弹窗的位置添加一个按钮,并绑定点击事件来显示弹窗。 ```html <button @click="show = true">显示验证码</button> ``` 4. 在弹窗引入 `geetest` 验证码库,并在 `mounted` 钩子函数初始化验证码。 ```html <template> <div> <popup v-model="show"> <div id="captcha"></div> </popup> </div> </template> <script> import Geetest from 'gt3-sdk' export default { data () { return { show: false } }, mounted () { // 初始化验证码 const captcha = new Geetest({ // 配置参数 gt: 'xxx', challenge: 'xxx', product: 'popup', lang: 'zh-cn' }) captcha.appendTo('#captcha') } } </script> ``` 5. 在弹窗添加一个确认按钮,并绑定点击事件来验证验证码。如果验证通过,关闭弹窗,并执行相应操作;如果验证失败,提示用户重新输入。 ```html <template> <div> <popup v-model="show"> <div id="captcha"></div> <button @click="submit">确认</button> </popup> </div> </template> <script> import Geetest from 'gt3-sdk' export default { data () { return { show: false } }, mounted () { // 初始化验证码 const captcha = new Geetest({ // 配置参数 gt: 'xxx', challenge: 'xxx', product: 'popup', lang: 'zh-cn' }) captcha.appendTo('#captcha') }, methods: { submit () { const result = captcha.getValidate() if (result) { // 验证通过,执行相应操作 // ... // 关闭弹窗 this.show = false } else { // 验证失败,提示用户重新输入 alert('请重新输入验证码') } } } } </script> ``` 以上代码仅为示例,具体实现可能需要根据实际情况进行调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值