事件系统与指令处理底层实现
Vue事件系统通过 addEventListener 绑定,指令通过钩子函数在patch和更新阶段执行生命周期回调。
事件绑定实现
VNode事件数据
JavaScript
// 模板: <button @click="onClick">
// VNode.data
{
on: {
click: { fn: onClick }
}
}
事件编译后存储在VNode的 data.on 对象中。
updateDOMListeners
JavaScript
function updateDOMListeners(oldVnode, vnode) {
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
const elm = vnode.elm
// 移除旧事件
for (const name in oldOn) {
if (!on[name]) {
elm.removeEventListener(name, oldOn[name])
}
}
// 添加新事件
for (const name in on) {
if (!oldOn[name]) {
elm.addEventListener(name, on[name])
}
}
}
diff对比新旧VNode事件,移除不再需要的事件,添加新事件。
事件修饰符处理
JavaScript
// @click.stop="handler"
function withModifiers(fn, modifiers) {
const wrapped = function(e) {
if (modifiers.includes('stop')) e.stopPropagation()
if (modifiers.includes('prevent')) e.preventDefault()
if (modifiers.includes('self') && e.target !== elm) return
if (modifiers.includes('once')) elm.removeEventListener(type, wrapped)
return fn(e)
}
return wrapped
}
修饰符通过包装函数在事件触发时执行对应方法。
指令系统实现
指令定义
JavaScript
Vue.directive('focus', {
bind(el, binding, vnode) {
// 指令第一次绑定到元素时调用
},
inserted(el, binding, vnode) {
// 被绑定元素插入父节点时调用
el.focus()
},
update(el, binding, vnode, oldVnode) {
// 所在组件更新时调用
},
componentUpdated(el, binding, vnode, oldVnode) {
// 所在组件及子组件更新后调用
},
unbind(el, binding, vnode) {
// 指令与元素解绑时调用
}
})
指令包含5个钩子函数,在不同生命周期阶段调用。
指令VNode存储
JavaScript
// 模板: <input v-focus>
// VNode.data
{
directives: [{
name: 'focus',
rawName: 'v-focus',
value: undefined,
expression: undefined
}]
}
指令信息存储在VNode的 data.directives 数组中。
指令钩子调用
JavaScript
function invokeDirectives(vnode, oldVnode, hooks) {
const dirs = vnode.data.directives
if (!dirs) return
for (let i = 0; i < dirs.length; i++) {
const dir = dirs[i]
const def = resolveAsset(vnode.context, 'directives', dir.name)
if (def && def[hooks]) {
def[hooks](vnode.elm, dir, vnode, oldVnode)
}
}
}
// patch阶段调用
// create阶段: bind -> inserted
// update阶段: update -> componentUpdated
// remove阶段: unbind
patch不同阶段调用对应指令钩子,传入元素、指令对象、VNode。
指令更新机制
JavaScript
// 组件更新时
function invokeUpdateDirectives(vnode, oldVnode) {
const dirs = vnode.data.directives
if (!dirs) return
for (let i = 0; i < dirs.length; i++) {
const dir = dirs[i]
const oldDir = oldVnode.data.directives?.[i]
const def = resolveAsset(vnode.context, 'directives', dir.name)
if (def.update) {
def.update(vnode.elm, dir, vnode, oldVnode)
}
}
}
组件更新时遍历指令调用 update 钩子,传入新旧VNode对比。
事件与指令协同
JavaScript
// 模板: <input v-model="msg" @input="onInput">
// VNode.data
{
directives: [{ name: 'model', value: msg }],
on: { input: onInput }
}
// patch过程
// 1. createElm: 创建元素
// 2. invokeCreateHooks: 调用指令bind
// 3. invokeInsertHooks: 调用指令inserted
// 4. updateDOMListeners: 绑定事件
指令钩子和事件绑定在patch不同阶段分别调用。
要点总结
- 事件编译后存储在VNode
data.on对象中 updateDOMListeners对比新旧VNode添加/移除事件监听- 修饰符通过包装函数在事件触发时执行对应方法
- 指令包含
bind/inserted/update/componentUpdated/unbind5个钩子 - 指令信息存储在VNode
data.directives数组 - patch不同阶段调用对应指令钩子,传入元素、指令对象、VNode
📝 发现内容有误?点击此处直接编辑