Vue事件绑定原理
Vue事件绑定原理
Vue
中通过v-on
或其语法糖@
指令来给元素绑定事件并且提供了事件修饰符,基本流程是进行模板编译生成AST
,生成render
函数后并执行得到VNode
,VNode
生成真实DOM
节点或者组件时候使用addEventListener
方法进行事件绑定。
描述
v-on
与@
用于绑定事件监听器,事件类型由参数指定,表达式可以是一个方法的名字或一个内联语句,如果没有修饰符也可以省略,用在普通元素上时,只能监听原生DOM
事件,用在自定义元素组件上时,也可以监听子组件触发的自定义事件,在监听原生DOM
事件时,方法以事件为唯一的参数,如果使用内联语句,语句可以访问一个$event property:v-on:click="handle('param', $event)"
,自2.4.0
开始v-on
同样支持不带参数绑定一个事件或监听器键值对的对象,注意当使用对象语法时,是不支持任何修饰器的。
修饰符
.stop
: 调用event.stopPropagation()
,即阻止事件冒泡。.prevent
: 调用event.preventDefault()
,即阻止默认事件。.capture
: 添加事件侦听器时使用capture
模式,即使用事件捕获模式处理事件。.self
: 只当事件是从侦听器绑定的元素本身触发时才触发回调。.{keyCode | keyAlias}
: 只当事件是从特定键触发时才触发回调。.native
: 监听组件根元素的原生事件,即注册组件根元素的原生事件而不是组件自定义事件的。.once
: 只触发一次回调。.left(2.2.0)
: 只当点击鼠标左键时触发。.right(2.2.0)
: 只当点击鼠标右键时触发。.middle(2.2.0)
: 只当点击鼠标中键时触发。.passive(2.3.0)
: 以{ passive: true }
模式添加侦听器,表示listener
永远不会调用preventDefault()
。
普通元素
1 | <!-- 方法处理器 --> |
组件元素
1 | <!-- 自定义事件 --> |
分析
Vue
源码的实现比较复杂,会处理各种兼容问题与异常以及各种条件分支,文章分析比较核心的代码部分,精简过后的版本,重要部分做出注释,commit id
为ef56410
。
编译阶段
Vue
在挂载实例前,有相当多的工作是进行模板的编译,将template
模板进行编译,解析成AST
树,再转换成render
函数,而在编译阶段,就是对事件的指令做收集处理。
在template
模板中,定义事件的部分是属于XML
的Attribute
,所以收集指令时需要匹配Attributes
以确定哪个Attribute
是属于事件。
1 | // dev/src/compiler/parser/index.js line 23 |
通过addHandler
方法,为AST
树添加事件相关的属性以及对事件修饰符进行处理。
1 | // dev/src/compiler/helpers.js line 69 |
代码生成
接下来需要将AST
语法树转render
函数,在这个过程中会加入对事件的处理,首先模块导出了generate
函数,generate
函数即会返回render
字符串,在这之前会调用genElement
函数,而在上述addHandler
方法处理的最后执行了el.plain = false
,这样在genElement
函数中会调用genData
函数,而在genData
函数中即会调用genHandlers
函数。
1 | // dev/src/compiler/codegen/index.js line 42 |
可以看到无论是处理普通元素事件还是组件根元素原生事件都会调用genHandlers
函数,genHandlers
函数即会遍历解析好的AST
树中事件属性,拿到event
对象属性,并根据属性上的事件对象拼接成字符串。
1 | // dev/src/compiler/codegen/events.js line 3 |
事件绑定
前面介绍了如何编译模板提取事件收集指令以及生成render
字符串和render
函数,但是事件真正的绑定到DOM
上还是离不开事件注册,此阶段就发生在patchVnode
过程中,在生成完成VNode
后,进行patchVnode
过程中创建真实DOM
时会进行事件注册的相关钩子处理。
1 | // dev/src/core/vdom/patch.js line 33 |
invokeCreateHooks
就是一个模板指令处理的任务,他分别针对不同的指令为真实阶段创建不同的任务,针对事件,这里会调updateDOMListeners
对真实的DOM
节点注册事件任务。
1 | // dev/src/platforms/web/runtime/modules/events.js line 105 |
最终添加与移除事件都是调用的add
与remove
方法,最终调用的方法即DOM
的addEventListener
方法与removeEventListener
方法。
1 | // dev/src/platforms/web/runtime/modules/events.js line 46 |
参考
1 | https://cn.vuejs.org/v2/api/#v-on |