示例:
<!-- 父组件 -->
<sub @cb="handle"></sub>
<!-- 子组件 -->
<script>
$emit('cb', 123);
</script>
Vue 2 的 $emit
机制本质上是一个基于发布-订阅模式的组件通信系统,其核心原理可分为以下几个关键环节:
sequenceDiagram
父组件->>子组件: 绑定 @cb="handler"
子组件->>子组件: 创建事件中心
子组件->>事件中心: 注册 cb 事件监听器
子组件->>子组件: 执行 this.$emit('cb', 123)
事件中心->>父组件: 调用 handler(123)
利用 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)
}
// 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
}
每个组件实例创建时都会初始化事件系统:
// src/core/instance/init.js
export function initEvents(vm: Component) {
vm._events = Object.create(null) // 事件存储对象
vm._hasHookEvent = false
}
当父组件模板中存在 @cb="handler"
时:
// 编译后的渲染函数代码示例
_c('child-component', {
on: {
"cb": function($event) { return handler($event) }
}
})
graph TD
A[子组件调用 $emit] --> B[查找 _events.cb 监听器数组]
B --> C{存在监听器?}
C -- 是 --> D[逐个执行回调函数]
C -- 否 --> E[无操作]
D --> F[父组件 handler 被执行]