Internal Implementation of $on
The $on method is responsible for subscribing to custom events. It registers callback functions into the instance's event storage object.
Vue.prototype.$on = function (eventName, handler) {
const hookPattern = /^hook:/
const instance = this
if (Array.isArray(eventName)) {
eventName.forEach(name => {
instance.$on(name, handler)
})
} else {
if (!instance._events[eventName]) {
instance._events[eventName] = []
}
instance._events[eventName].push(handler)
if (hookPattern.test(eventName)) {
instance._hasHookEvent = true
}
}
return instance
}
When the first argument is an array, the method recursively subscribes to each event name. Otherwise, it initializes the event array if needed and adds the handler. The _hasHookEvent flag optimizes lifecycle hook event processing.
Internal Implementation of $emit
The $emit method triggers registered callbacks for a specific event.
Vue.prototype.$emit = function (eventName) {
const instance = this
const handlers = instance._events[eventName]
if (handlers) {
const params = Array.from(arguments).slice(1)
handlers.forEach(callback => {
callback.apply(instance, params)
})
}
return instance
}
The method retrieves the handler array from _events, extracts additional arguments, and invokes each callback with the instance as context.
Event Flow in Parent-Child Communication
When a parent component uses @customEvent="callback" on a child component, Vue internally registers the callback via $on on the child's event center. The child then triggers the event using $emit, which executes the parent's callback.
Event Bus Pattern for Non-Parent-Child Communication
For communication between sibling components or unrelated components, an Event Bus provides a simple solution without state management libraries.
First, create a shared Vue instance:
// eventBus.js
import Vue from 'vue'
const bus = new Vue()
export default bus
Components can then communicate through this shared instance:
// ComponentA.vue - Subscribing to events
import bus from './eventBus'
export default {
name: 'ComponentA',
created() {
bus.$on('notify-user', (payload) => {
console.log('Received:', payload)
})
},
beforeDestroy() {
bus.$off('notify-user')
}
}
// ComponentB.vue - Emitting events
import bus from './eventBus'
export default {
name: 'ComponentB',
methods: {
sendNotification() {
bus.$emit('notify-user', {
message: 'Hello from ComponentB',
timestamp: Date.now()
})
}
}
}
This pattern leverages the same $on and $emit mechanics, but uses a standalone Vue instance as the event hub, allowing any component with access to the bus to publish or subscribe to events.