Vue下优雅实现事件频率控制

该方式在vue2.4以后不再可用,之后的版本,在绑定事件的时候,内部给函数有包装了一层,我们无法在指令内部移除事件监听。源码见 ->->->这里

在web开发中,对DOM事件做频率限制随处可见,特别是涉及网络请求。这样的需求下,我们希望事件在第一次触发后立即响应,然后不接受响应,接下来在特定的时间内没再次收到该事件,则解除该限制。

借鉴debouncethrottle的思路,我们第一次可能想到的是自己定义一个频率控制的高阶函数(比如oneTime),接受我们传入的事件响应函数作为参数,得到一个新的函数传入@click里面,就像:

<button @click="oneTime(myFn)>测试</button>

经过实验,发现这样不行。查看vue.js源码

export function createFnInvoker (fns: Function | Array<Function>): Function {
  function invoker () {
    const fns = invoker.fns
    if (Array.isArray(fns)) {
      const cloned = fns.slice()
      for (let i = 0; i < cloned.length; i++) {
        cloned[i].apply(null, arguments)
      }
    } else {
      // return handler return value for single handlers
      return fns.apply(null, arguments)
    }
  }
  invoker.fns = fns
  return invoker
}

可发现,我们在模版里面传入的部分,也就是函数中的fns,包裹在invoker中每次都会被执行,也就是绑定的过程每次点击都会进行。所以,这样行不通。
那么,我们只能在data里面单独定义一个变量来保存生成的函数,然后把这个变量塞入@click里面。这样就导致每次需要使用该特性都得单独定义一个变量来保存新的函数,更优雅地实现方式是咋样的呢?我首先想到的是指令,想象下,在需要控制频率的地方使用自定义的指令,传入需要被控制的事件及时间,一切都和谐多了。附上指令实现方式:

function oneTime(method, duration) {
    let timer = null,running = false;
    return function (...args) {
        clearTimeout(timer);
        timer = setTimeout(()=>{
            running = false
        }, duration);
        if(running)return;
        running = true;
        method.apply(this, args);
    }
}

Vue.directive('freq', { 
    inserted(el,bd,vnode) { 
            console.log('here',vnode.data.on)
        let all = {};
        if(typeof bd.value === 'string'){
            all[bd.value] = 500
        }else if(Array.isArray(bd.value)){
            bd.value.forEach(val=>all[val] = 500)
        }else{
            all = bd.value
        }
        for(let ev in all){
            if(!vnode.data.on[ev])continue;
            el.removeEventListener(ev,vnode.data.on[ev]);
            vnode.data.on[ev] = oneTime(vnode.data.on[ev],all[ev])
            el.addEventListener(ev,vnode.data.on[ev])
        }
    }
})

然后我们就可以这样使用了:

<button @click="fn" v-freq="{click:500}">
    测试
</button>

或者这样:

<button @click="fn" v-freq="'click'">
    测试
</button>

甚至这样:

<button @click="fn" v-freq="['click','mouseenter']">
    测试
</button>