本节的示例代码如下
经过上一节对原生事件的分析,我们已经知道了事件parse、codegen、invoker的流程。因此本节对自定义事件只做关键逻辑说明
parse
我们知道,在parse阶段,当对一个标签完整匹配后将调用parseEndTag执行end调用closeElement进行process作元素的加工,这包括了对标签元素的processAttrs。加工后的ast节点将被标记events对象作为事件描述。故
父组件ast如下
子组件ast如下
codegen
我们知道vue会将生成的ast节点转为code,这是一次genElement递归的过程,每一次都会调用gendate执行genHandlers对事件进行处理,并最终将事件描述提取为on对象并作为_c的参数二,故
父组件的code如下
子组件的code如下
invoke
我们知道,一个vue组件在呈现到页面之前,会执行init-mount-render-update-patch等流程。在render过程中其实调用了我们code中的_c去生成vnode,在创建vnode的时候会根据_c的第一个参数(child)确定创建的到底是元素节点还是组件节点
由于我们当前的tag不是一个html元素,故将执行到框红的位置去创建组件vnode,根据之前分析,我们知道这将创建一个组件构造器,并在patch阶段对组件执行init。除此之外,还对我们的events做了处理
这里将我们的事件描述对象on缓存到了listeners上,并将nativeOn替换到了原先的on上。nativeOn只得是我们的原生dom事件,当我们在组件上写.native修饰符时vue将在code阶段在生成nativeOn属性。
接着便去创建组件的vnode
接着便会执行_update,进入父组件的patch流程,在父组件进行createElement过程中将会对组件调用createComponent,执行子组件的创建过程,即init-mount-render-update-patch
在子组件的init过程中,将会对配置对象进行一次合并,执行initInternalComponent
接着调用initEvents
可以看到,框红一的位置取到了我们在创建组件vnode时向listeners保存的事件修饰对象,接着调用updateComponentListeners
经过上一节的分析,我们知道,updateListeners就是我们事件处理的核心,它将向我们的dom上绑定事件
但是与上一次不同的是,本次传递的add函数和上一次不一样
上一次
本次
也就是说,对于自定义事件,并不是使用addEventListener对dom添加事件的
我们来看$on的实现,它会将我们的添加到组件vnode的events属性上,key为事件名,value为对应的回调函数
接着执行$mount,进入子组件的parse和codegen过程,之后进入子组件的update,并最终执行到patch,走到子组件的patch之后,它其实就和我们原生事件是一样的了,故这里忽略掉
invoker
当点击emit parent时将触发原生事件click触发我们的回调onEmitParent函数,调用this.$emit函数。而this.$emit函数则是在vue原型行定义的,它会拿到我们在$on阶段向组件vnode添加的事件并执行