有没有写过vue自定义指令,原理是什么?.m
背景看过一些关于自定义命令的文章,但是探究其原理的文章并不多,所以我决定发一篇。如何自定义命令?其实关于这个问题,官方文档中已经有很好的例子了,先回顾一下过去吧。除了核心功能(v-model和v-show)的默认内置指令外,Vue还允许注册自定义指令。注意,在Vue2.0中,代码复用和抽象的主要形式是组件。但是,在某些情况下,您仍然需要对普通DOM元素进行低级操作,这时就会用到自定义指令。获得焦点的输入框示例如下:当页面加载时,该元素将获得焦点(注意:自动对焦在移动版Safari上不起作用)。事实上,只要你在打开页面后没有点击任何东西,输入应该仍然是焦点。现在让我们用一个指令来实现这个功能://注册一个全局自定义指令`v-focus`Vue.directive('focus',{//当绑定的元素被插入到DOM中时...inserted:function(el){//焦点元素el.focus()}})如果要注册本地指令,该组件还接受一个指令选项:directives:{focus:{//插入指令的定义:function(el){el.focus()}}}然后你可以在模板中的任何元素上使用新的v-focus属性,如下所示:指令中提供的钩子函数指令定义对象可以提供以下钩子函数(两者都是可选的):bind:仅在指令第一次绑定到元素时调用一次。可以在这里进行一次性的初始化设置。inserted:绑定元素插入到父节点时调用(只保证父节点存在,不一定插入到文档中)。update:组件的VNode更新时调用,但也有可能发生在其子VNode更新之前。指令的值可能已更改,也可能未更改。但是可以通过比较更新前后的值来忽略不必要的模板更新(钩子函数参数详见下文)。componentUpdated:命令所在组件的VNode及其子VNode全部更新后调用。unbind:仅在指令与元素解除绑定时调用一次。钩子函数参数指令的钩子函数会传递以下参数:el:指令绑定的元素,可以用来直接操作DOM。binding:包含以下属性的对象:name:指令的名称,不包括v-前缀。value:指令的绑定值,例如:在v-my-directive="1+1"中,绑定值为2。oldValue:指令绑定的前一个值,仅在update和componentUpdated钩子中可用。无论值是否已更改均可用。expression:字符串形式的指令表达式。例如,在v-my-directive="1+1"中,表达式为"1+1"。arg:传递给命令的参数,可选。例如,在v-my-directive:foo中,参数是“foo”。修饰符:包含修饰符的对象。例如:在v-my-directive.foo.bar中,修饰符对象为{foo:true,bar:true}。vnode:Vue编译生成的虚拟节点。更多详细信息可以参考官网的VNodeAPI。oldVnode:前一个虚拟节点,仅在update和componentUpdatedhooks中可用。除了el,其他参数应该是只读的,不应该被修改。如果需要在hook之间共享数据,建议通过元素的数据集来完成。我们来看一个演示,
Vue.directive('demo',{bind:function(el,binding,vnode){vars=JSON.stringifyel.innerHTML='name:'+s(binding.name)+'
'+'value:'+s(binding.value)+'
'+'表达式:'+s(binding.expression)+'
'+'参数:'+s(binding.arg)+'
'+'修饰符:'+s(binding.modifiers)+'
'+'vnodekeys:'+Object.keys(vnode).join(',')}})newVue({el:'#hook-arguments-example',data:{message:'hello!'}})来看看一下处理的结果:name:"demo"value:"hello!"expression:"message"argument:"foo"modifiers:{"a":true,"b":true}vnodekeys:tag,数据、子项、文本、榆树、ns、上下文、fnContext、fnOptions、fnScopeId、键、componentOptions、componentInstance,parent,raw,isStatic,isRootInsert,isComment,isCloned,isOnce,asyncFactory,asyncMeta,isAsyncPlaceholder指令实现原理分析通过上面官网的例子和我们平时的编码,我们基本了解了Vue的指令是怎么用的,接下来我们从源码的角度分析其实现原理Vue.directive的定义:functioninitAssetRegisters(Vue){ASSET_TYPES.forEach(function(type){Vue[type]=function(id,definition){if(!definition){returnthis.options[type+'s'][id]}else{if(type==='component'){validateComponentName(id);}if(type==='component'&&isPlainObject(definition)){definition.name=definition.name||id;definition=this.options._base.extend(definition);}if(type==='directive'&&typeofdefinition==='function'){//提示:包容传参definition={bind:definition,update:definition};}//提示:存储一个['component','directive','filter']this.options[type+'s'][id]=定义;返回定义}};});}详见前端高级面试题。其实这个方法比较简单。就是在vm.options.directives中挂载一个mapping,比如vm.$options.directives.demo={xxx},我们要看这条指令是如何生效的。在下一步分析源码之前,我们可以大概猜到自定义指令是如何实现的。在模板编译阶段,将元素的属性解析为指令的属性,在元素生命周期的不同阶段调用自定义指令中的不同自定义逻辑。接下来结合源码进行分析,将这条指令的解析和生效分为三个阶段:模板编译阶段、VNode生成阶段、生成真实Dom的patch阶段。我们以下面的代码片段为例:
在模板编译阶段,不熟悉模板的同学编译可以回顾一下,这个阶段大致做了些什么。这里就不细说了,只关注指令部分。directive是元素属性的一部分,所以在解析tag元素时,会放到EleAst对象的attrs属性中。上面的例子会被解析如下:[{name:"id",value:"hook-arguments-example",start:5,end:32},{name:"v-demo:foo.a.b",value:"message",start:33,end:57}]当匹配到结束标签时,这些属性将被进一步处理,例如:如果是指令,则将其作为指令处理并挂载到EleAst对象上.具体过程如下。当endTagMatch匹配到结束标签时,它会调用处理结束标签的parseEndTag函数。该函数内部会调用parseHtml配置项的options.end,回调closeElement。functioncloseElement(element){//...if(!inVPre&&!element.processed){element=processElement(element,options);}//...}注意这里的processElement方法主要是解析元素进行各种处理。让我们看一下processElement的代码。函数processElement(元素,选项){processKey(元素);processRef(元素);processSlotContent(元素);processSlotOutlet(元素);流程组件(元素);//...processAttrs(元素);对于元素属性的处理方法,我们需要关注processAttrs方法,这是一个处理指令和修饰符相关的方法。我们看一下processAttrs的伪代码:functionprocessAttrs(el){varlist=el.attrsList;vari,l,name,rawName,value,modifiers,syncGen,isDynamic;对于(i=0,l=list.length;i