美文网首页
js debounce函数的一点思考

js debounce函数的一点思考

作者: 一个废人 | 来源:发表于2018-03-07 23:28 被阅读19次

    我是一个比较笨的人,有些逻辑上不好拐大弯,一旦拐太大了容易撞车,搞不明白。今天我在做input[type=password]的过程中,需要对用户输入的密码进行格式检查,但我又不想用户每输入一次就检查一次,而是希望让程序发现用户输入停止1000毫秒后再检查(虽说只是前端验证,做debounce并不在性能上有多大优化,但我就想做),之前知道debounce函数,于是自己想试一下。

    <input type="text" name="" value="" class="flex-2" @input="inputHandler($event)">
    

    首先想到的是用lodash里自带的debounce函数,于是就有了

    import { debounce } from 'lodash'
    ...
    inputHandler(event){
          let password = event.target.value
          debounce(()=>{
              let reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\S]{8,32}$/;
              if (reg.test(password) !== true) {
                console.log(11111)
              }
          }, 1000)
    },
    

    结果发现console.log(11111)根本没有执行。
    无奈,请求同事,什么电梯的比喻、高阶函数、你应该把inputHandler放到debounce里面云云,因我比较笨,实在没听懂。
    没辙,只好回家重新思索,追溯本源,这样让我好受一些。

    前面提到,做debounce的主要意图,是想让用户输入间隔1秒后再去对密码格式进行检查,提到1秒后,条件反射般想到的便应是:

    setTimeout(()=>{
      console.log('如何如何')
    },1000)
    

    撇开那些高深的东西,我们先把这段代码加到inputHandler中

    inputHandler(event){
          let password = event.target.value
          setTimeout(()=>{
            let reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\S]{8,32}$/;
            if (reg.test(password) !== true) {
              console.log(11111)
            }
          },1000)
    },
    

    这时在input里输入,发现console.log(11111)执行了,万里长城第一步。
    接下来的问题,想必大家也知道,就是我在input里输入了3个字母,那么11111就会在控制台里出现3次,输入6个字母则出现6次,这时并没有达到我们想要的debounce的目的。
    而出现这个的原因是,每在input里输入一个字母,inputHandler就会执行一次。输入6个字母,setTimeout便执行6次,所以控制台出现6个11111
    那我们直观的想,如果setTimeout可以只执行一次就好了。
    于是,逻辑就变成,如果在1秒内又一次执行了inputHandler,那么就让上一次的setTimeout取消,并重新执行新的setTimeout。而正好,我们知道一个取消setTimeout的方法,即clearTimeout
    通过阅读MDN文档,我们得知:

    setTimeout返回值timeoutID是一个正整数,表示定时器的编号。这个值可以传递给clearTimeout()来取消该定时。

    于是我们知道,只要clearTimeout(setTimeout的返回值),就可以取消该定时。
    我们新增一个变量timeoutID记录当前setTimeout的号码,以便在合适的时候执行clearTimeout(timeoutID):

    inputHandler(event){
          let password = event.target.value
          let timeoutID = setTimeout(()=>{
            let reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\S]{8,32}$/;
            if (reg.test(password) !== true) {
              console.log(11111)
            }
          },1000)
    },
    

    以合理的逻辑推理,我们肯定要在inputHandler执行的时候就清空上一次的setTimeout,我们将clearTimeout(timeoutID)加入到代码中:

    inputHandler(event){
          clearTimeout(timeoutID)
          let password = event.target.value
          let timeoutID = setTimeout(()=>{
            let reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\S]{8,32}$/;
            if (reg.test(password) !== true) {
              console.log(11111)
            }
          },1000)
    },
    

    但问题来了,此时timeoutID并没有声明,所以clearTimeout此处无效。更何况而且每次我们都重新声明了timeoutID,那么clearTimeout的是当前的setTimeout。
    那么如何清空上一次的timeoutID呢?这里就要用到全局变量了:

    let timeoutID
    inputHandler(event){
          clearTimeout(timeoutID)
          let password = event.target.value
          timeoutID = setTimeout(()=>{
            let reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[\S]{8,32}$/;
            if (reg.test(password) !== true) {
              console.log(11111)
            }
          },1000)
    },
    

    当页面加载后,全局变量timeoutID就始终存在于内存中,第一次在input里输入字母时,因为timeoutID还未赋值,故第一次clearTimeout(timeoutID)虽然执行了但并无意义。
    let password = event.target.value之后,代码将setTimeout的返回值赋值给timeoutID。只要我们在1秒内第二次在input内输入字母,那么第二次执行clearTimeout(timeoutID)时,此时清除的timeoutID为上一次setTimeout的返回值。随着代码继续执行,便将第二次的setTimeout返回值赋给了timeoutID。如果此时不再input输入,那么1秒后,console.log(11111)将执行。这时,我们就实现了debounce效果。

    优化

    虽然功能实现了,但是有一个丑陋的全局变量timeoutID,能不能重构代码不用全局变量呢?
    我想到的是,写一个初始化函数init(),在里面赋值timeoutID,页面加载时调用init(),timeoutID就称为全局的了。

    init(){
      let timeoutID
    }
    

    未完待续。

    相关文章

      网友评论

          本文标题:js debounce函数的一点思考

          本文链接:https://www.haomeiwen.com/subject/uymsfftx.html