VUE从零封装一个防抖节流抽象组件

505 阅读4分钟

灵感来源 

在项目开发的过程中 测试提出了一个优化问题**'每点击一次就会弹出一条提示语'(what fuck?难道不该点一次就出一条提示语吗?)**好吧 我是个职业素质很高的程序员,所以第一时间想到了用防抖函数来解决.

什么是防抖?

首先用一个最简单的例子介绍一下什么是防抖,玩过LOL的朋友都应该知道,英雄在释放技能时只会有前摇的,所以这个技能前摇就可以称为防抖.假想一下 如果一个英雄的技能没有任何前摇 那其他玩家是不是没有任何办法克制他 预判他.所以在编程中我们可以写一个防抖函数 防止某些213用户一直点 造成提示无限出现.

防抖函数

可以直接复制代码到F12 console中尝试

function debounce(fn, time) { 
//接收一个函数和延迟时间          
  let timer = null   //初始化            
    return function(...args) {   //返回函数接收参数               
     if (timer)clearTimeout(timer)   //如果timer有值那就清空它 再重新计时               
     timer = setTimeout(() => {   //开始计时 两秒后进行函数                    
       fn.apply(this, args)  //将return的函数this指向实际调用者(本文中为func)               
     }, time)            
    }        
}
let func=debounce(()=>{console.log('1')},1000)
func()

什么是节流?

再用一个最简单的例子介绍一下,节流就是技能CD,当你释放一个技能必须再过N秒之后才能释放.在程序中一般用在对某些特殊事件的监听后的DOM操作上.如:滚动条 鼠标移动事件.

节流函数

function throttle(fn, time) { //接收一个函数和延迟时间            
    let pre = 0      //初始化           
     return function(...args) {   //返回函数接收参数                
        let now = +new Date()    //定义一个变量接收最新的时间搓                
        if (now - pre > time) {  //判断如果最新时间减去初始化时间大于延迟时间就执行                    
        fn.apply(this, args)   //将return的函数this指向实际调用者(本文中为func)                    
        pre = now             //将开始时间赋值为上一次的结束时间               
        }           
     }        
}

组件想法的来源

函数都写好了,但是我突然想到一个项目里有这么多点击事件需要我去做防抖,节流操作,就算我把函数封装在一个JS里 光在每个页面中导入也很麻烦,有没有渐变的方法呢?我就在百度搜索了一下发现这篇分享的文章.blog.csdn.net/userkang/ar…

这篇文章写得很不错,原理是利用VUE的抽象组件的方法,但是并不能'直接'用在我的项目中,因为我司开发的项目都是基于elementUI框架,所有的事件和方法并不跟原生一样存放在vnode.data.on中  elementUI的事件存放在vnode.componentOptions.listeners中,所以我对这个组件进行了二次封装,将其适配HTML原生的标签和elementUI的标签.

所以我做了以下改动

  1. 适配原生以及elementUI标签

  2. 可自由选择组件功能  防抖or节流  适应不同的业务需求

    import Vue from 'vue'
    
    const debounce = (func, time, ctx, immediate) => {
      let timer
      const rtn = (...params) => {
        clearTimeout(timer)
    
        if (immediate) {
          let callNow = !timer
          console.log(timer)
          timer = setTimeout(() => {
            timer = null
          }, time)
          if (callNow) func.apply(ctx, params)
        } else {
          timer = setTimeout(() => {
            func.apply(ctx, params)
          }, time)
        }
      }
      return rtn
    }
    
    const throttle = (func, time, ctx, immedidate) => {
        let pre
        if (immedidate) {
            pre = 0
        } else {
            pre = +new Date()
        }
        return function(args) {
            let now = +new Date()
            if (now - pre > time) {
                func.apply(ctx, args)
                pre = now
            }
        }
    }
    
    Vue.component('Debounce', {
      abstract: true,
      props: ['time', 'events', 'immediate', 'way', 'proto'],
      created() {
        this.eventKeys = this.events && this.events.split(',')
        this.types = this.way || '防抖'
        this.prototype = this.proto || 'element'
      },
      render() {
        const vnode = this.$slots.default[0]
        // 如果默认没有传 events,则对所有绑定事件加上防抖
        if (!this.eventKeys) {
            this.prototype == 'element' ? this.eventKeys = Object.keys(vnode.componentOptions.listeners) : this.eventKeys = Object.keys(vnode.data.on)
        }
        if (this.types == '防抖') {
            if (this.prototype == 'element') {
                this.eventKeys.forEach(key => {
                    vnode.componentOptions.listeners[key] = debounce(
                      vnode.componentOptions.listeners[key],
                      this.time,
                      vnode,
                      this.immediate
                    )
                  })
            } else {
                this.eventKeys.forEach(key => {
                    vnode.data.on[key] = debounce(
                      vnode.data.on[key],
                      this.time,
                      vnode,
                      this.immediate
                    )
                  })
            }
        } else {
            if (this.prototype == 'element') {
                this.eventKeys.forEach(key => {
                    vnode.componentOptions.listeners[key] = throttle(
                      vnode.componentOptions.listeners[key],
                      this.time,
                      vnode,
                      this.immediate
                    )
                  })
            } else {
                this.eventKeys.forEach(key => {
                     vnode.data.on[key] = throttle(
                       vnode.data.on[key],
                       this.time,
                       vnode,
                       this.immediate
                    )
                  })
                }
        }
        return vnode
      }
    })
    
    import '@/common/debounce' // 导入防抖节流组件
    /**
     * @param {原生还是element,默认为element且只要值不是element皆为原生} proto
     * @param {需要进行防抖的事件名,默认为所有} event
     * @param {延迟时间} time
     * @param {选择防抖节流,默认为防抖且只要值不是防抖皆为节流} way
     * PS 为什么way的方法属性值 不用英文?我怕公司同事不会拼debounce 所以还是写中文吧
     * @param {是否立即执行} immediate
     */
    
    //element用法
    <Debounce event='click' :time='500'  :immediate='true' way='防抖' proto='element'>
    <el-button @click="submitCheck()" >确认提交</el-button>
    </Debounce>
    
    //原生用法
    <Debounce event='click' :time='500'  :immediate='true' way='节流' proto='html'>
    <button @click="submitCheck()" >确认提交</button>
    </Debounce>```