本节沿用上一节代码

经过parse分析,我们知道了vue在匹配到结束标签时会调用parseEndTag对ast元素节点进行加工,并最终生成一个带events标记的ast节点;在codegen阶段,则会调用gendata执行addHandler对事件进行处理,并生成on对象来对事件进行描述,每一个key都对应一个匿名函数,并将我们定义的函数作为返回值返回,因为在返回我们自己定义的函数之前vue还要对event对象、自定义修饰符进行处理,每一个自定义的修饰符都对应着一段特别的代码,如.stop修饰符最终对象的匿名函数将如下
function($event){
$event.stopPropagation()
return onConsole($event)
}
这小节,我们将分析在运行时阶段,这些函数将如何被调用(invoke)
我们知道path函数是经过createPatchFunction返回的,它接收一个对象,nodeOps是与dom操作相关的api

modules则是处理模块相关的api

可以看到,在生成path函数的过程中已经将事件相关的api传入了,那么我们看下createPatchFunction

可以看到,首当其冲的向cbs中保存了一些值,值则来自于hooks

结合我们事件模块导出的结果,我们推断出,cbs中缓存了事件的create和update函数

而它的调用则是在子元素被创建但未被插入时,即

即

在对cbs的遍历过程中将执行到事件模块导出的函数,即updateDOMListeners

调用updateListeners

框红的位置,是对应我们第一次patch,那时是没有旧节点的。那么理所应当的,框黄的位置,则对应组件更新阶段的patch
首先看框红的内容,调用createFnInvoker,传入我们定义的回调函数

可以看到,框红的位置将我们传入的回调函数挂载到了invoker函数上并返回,因此on.click=invoker,而involer中又调用了invokeWithErrorHandling

显然,在这里对函数进行了调用。那么现在关键的就是什么时候调用了invoker函数(a)
返回updateListeners框红的位置,向下判断是否定义了once修饰符,如果定义了则执行createOnceHandler,即remove函数

多么熟悉的api啊,也就是说once之所以只执行一次,是因为执行后就被释放掉了
返回updateListeners框红的位置,调用add函数

框红一的位置在invoker调用之前加了一些判断代码
框红二的位置则调用原生dom api绑定dom事件
此时,当点击时便会调用invoker函数了(a),在invoker中将会对我们定义的函数进行遍历,并在invokeWithErrorHandling进行调用,这将最终调用到我们代码中定义的onConsole函数

返回updateListeners框黄的位置,当第二次组件更新阶段,旧vnode有值,故将走进判断。可以看到这里只是简单的将cur挂载到了old.fns上。而cur和old都是获取的vnode上的on属性,我们知道,在ast节点创建过程中会将事件描述到events上,在代码生成阶段会将events转化为on属性,这一过程是只执行一次,因为我们不可能将我们写的代码进行非手动删除。那么在add阶段,则针对不同的事件类型在元素上添加过回调,故再次render期间,元素的回调仍是存在的。又由于回调执行的实际上是fns,故我们只需要替换fns的引用即可(b)
那么,组件更新过程中做了哪些事情呢,为什么能再次走到(b)呢?
我们知道,组件更新会再次patch,并调用patchVnode,并在该函数中再次遍历cbs

由于cbs中保存了事件处理模块的api,故和第一次一样会执行到updateDOMListeners,只不过这次的oldvnode是有值的
