全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-20 10 分钟 ✍️ juanwangdev

事件系统与指令处理底层实现

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/unbind 5个钩子
  • 指令信息存储在VNode data.directives 数组
  • patch不同阶段调用对应指令钩子,传入元素、指令对象、VNode

📝 发现内容有误?点击此处直接编辑

← 上一篇 KeepAlive与缓存机制底层实现
下一篇 → 响应式系统与依赖追踪
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库