Understanding Vue's Event System: $on and $emit Methods

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.

Tags: Vue.js Event System javascript Component Communication frontend

Posted on Fri, 21 Aug 2026 16:12:38 +0000 by coltrane