示例:

<!-- 父组件 -->
<sub @cb="handle"></sub>

<!-- 子组件 -->
<script>
$emit('cb', 123);
</script>

Vue 2 的 $emit 机制本质上是一个基于发布-订阅模式的组件通信系统,其核心原理可分为以下几个关键环节:

一、核心流程示意图

sequenceDiagram
父组件->>子组件: 绑定 @cb="handler"
子组件->>子组件: 创建事件中心
子组件->>事件中心: 注册 cb 事件监听器
子组件->>子组件: 执行 this.$emit('cb', 123)
事件中心->>父组件: 调用 handler(123)

二、源码级实现原理

1. 事件监听注册阶段(父组件)

利用 vm.$on 注册监听

// src/core/vdom/create-component.js
function updateComponentListeners(
  vm: Component,
  listeners: Object,
  oldListeners: ?Object
) {
  // 将 @cb="handler" 转换为子组件的事件监听
  updateListeners(listeners, oldListeners || {}, (event, fn) => {
    vm.$on(event, fn) // 关键:将父组件回调注册到子组件事件中心
  }, vm)
}

2. 事件触发阶段(子组件)

// src/core/instance/events.js
Vue.prototype.$emit = function (event: string): Component {
  const vm: Component = this
  let cbs = vm._events[event]
  if (cbs) {
    const args = toArray(arguments, 1)
    for (let i = 0, l = cbs.length; i < l; i++) {
      // 执行父组件传递的回调函数
      cbs[i].apply(vm, args)
    }
  }
  return vm
}

三、关键机制详解

1. 事件中心初始化

每个组件实例创建时都会初始化事件系统:

// src/core/instance/init.js
export function initEvents(vm: Component) {
  vm._events = Object.create(null) // 事件存储对象
  vm._hasHookEvent = false
}

2. 事件绑定原理

当父组件模板中存在 @cb="handler" 时:

// 编译后的渲染函数代码示例
_c('child-component', {
  on: {
    "cb": function($event) { return handler($event) }
  }
})

3. 事件派发流程

graph TD
A[子组件调用 $emit] --> B[查找 _events.cb 监听器数组]
B --> C{存在监听器?}
C -- 是 --> D[逐个执行回调函数]
C -- 否 --> E[无操作]
D --> F[父组件 handler 被执行]