vue自定义指令,实现事件的防抖和节流(暂未实现表达式解析和模板编译豁免)

一、vue事件绑定的原理

先看一个基础的vue绑定事件的代码

<template>
<div v-on:click="test" v-on:mousedown="test1('a',2)" v-on:mouseup="test2($event,'a',b)"></div>
</template>
<script>
export default Vue.extend({
    data: () => {
        return {
            b: null
        }
    },
    methods:{
		test(){
			
		},
		test1(param1,param2){
			
		},
		test2(event,param1,param2){
			
		}
	}
});
</script>

这个没什么好说的v-on:click=“test”,其实就是click执行的test方法
但是后面两个test1(‘a’,2)和test2($ event,‘a’,b)
其实是在模板编译时被豁免了(想不出其他词汇了)
因为按照表达式编译test1(‘a’,2)和test2($ event,‘a’,b)其实在编译时就是会执行对应的test1和test2函数的,但实际上并没有执行,因为v-on在模板编译时,将两者编译成了类似如下的代码

//test1
function(){
	return function(){
	//context是vue上下文
		context.test1('a',2)
	}
}
//test2
function(){
	return function(){
	//context是vue上下文
		context.test2(window.Event,'a',context.b)
	}
}

其实就是最终编译出来的表达式其实v-on:后面还是函数而不是函数执行的表达式
这次编译vue做了两件事,
1.表达式编译
2.利用闭包将函数执行表达式编译成函数

二、自定义指令实现事件防抖和节流的意义

防抖函数debounce和节流函数throttle,网上一抓一大把,此处不讨论,
此处讨论的是为何在vue指令中实现,
最大的原因,还是懒啊。。。
谁也不想每次都import这两个函数进来
有人之前实现过这个指令的,但是效果很差,
如何在Vue3中实现自定义指令(超详细!)
为啥效果很差。。。。
看最后他是怎么调用的就知道了

<input v-throttle="{fn: handleInput, event: 'input', delay: 1000}" />

有两个问题,1是如果需要handleInput传入参数的情况时,没法传入额外参数
2.本来简洁的v-on:input="handleInput"成了这鸟样-=

因此理想状态下的实现方式应该时这样的

<input v-throttle:input:300="test1('a',2)" v-debounce:click:500="test2($event,'a',b)" />

v-throttle:input:300表示节流没300毫秒执行一次input事件绑定的函数,而且支持传入参数
v-debounce:click:500 表示间隔500毫秒后才执行click事件的函数

三、实现方式

throttle和debounce函数网上自己找吧。用的是vue-property-decorator写法

function analysisExpression(_this,expression){
    let fName,args;
    if(expression.indexOf("(")>-1){
        fName = expression.substr(0,expression.indexOf("("));
        let argStr = expression.substr(expression.indexOf("(")+1,expression.indexOf(")")-expression.indexOf("(")-1);
        args=argStr.trim().length>0?argStr.split(","):[];
        args = args.map(v=>{
            if((v.startsWith("'") && v.endsWith("'"))||(v.startsWith("\"") && v.endsWith("\""))){
                return v.substr(1,v.length-2);
            }
            if(!isNaN(v)){
                return Number(v);
            }
            if(v==null||v==undefined){
                return v;
            }
            if(v.toLowerCase()==='$event'){
                return v.toLowerCase();
            }
            if(isNaN(v)){
                let firstName = v.split(".")[0].trim();
                if(window[firstName]){
                    return eval(v);
                }
                else{
                    return _this[v];
                }
            }
        })
    }
    else{
        fName=expression;
        args=[];
    }
    return {
        fun:_this[fName],
        args:args
    }
}
function bindDirective(fun: Function) {
    return (el, binding, vnode) => {
        let expression = binding.expression;
        console.log(expression);
        if (expression) {
            let args = (binding.arg || 'click:500').split(":");
            let event = args[0] || 'click';
            let t = parseInt(args[1] || '500');
            let res = analysisExpression(vnode.context,expression.trim());
            console.log(res);
            el.addEventListener(event, fun((e) => {
                res.fun.apply(vnode.componentInstance,res.args.map(v=>{
                    if(v==='$event'){
                        return e;
                    }
                    return v;
                }));
            }, t));
        }
    }
}
@Component({
    directives: {
        debounce: {
            bind: bindDirective(debounce)
        },
        throttle:{
            bind: bindDirective(throttle)
        },
        throttle1:{
            bind: bindDirective(throttle1)
        }
    }
})

代码中最关键的,也是最核心的就是analysisExpression,就是对指令的表达式的解析,目前只是最基础的解析,v-on的解析vue没有开放对应的api,其实可以直接借助那个解析函数的,另外还有就是编译时的豁免权,这个代码有个致命的问题,就是
v-throttle:input:300=“test1(‘a’,2)” v-debounce:click:500=“test2($event,‘a’,b)”
会直接执行test1和test2函数,因为没有进行编译豁免。。。
目前就这两点。。。。

你可能感兴趣的:(vue.js,javascript,前端)