element/src/mixins/emmiter.js
// 递归广播,查找当前组件的子组件名称和目标组件名称是否相同,不相同则继续深度递归(感觉挺好性能)
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
// child.$on(eventName, fn) 组件捕获事件形式
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
//parent存在并且(name不存在或者name!=componentName)进入while
//如果parent不存在或者获取到的name和传入的componentName相等退出while
while (parent && (!name || name !== componentName)) {
//vue 父组件
parent = parent.$parent;
if (parent) {
//父组件的组件名称
name = parent.$options.componentName;
}
}
//得到目标组件
if (parent) {
//执行$emit方法,最中执行vue底层$on上绑定的eventName事件
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
event.js vue源码路径vue/src/core/instance/event.js
event emmiter 一般实现如下api
on添加事件,off移除on上的事件,emmit触发on上的事件
/* @flow */
import {
tip,
toArray,
hyphenate,
formatComponentName,
invokeWithErrorHandling
} from '../util/index'
import { updateListeners } from '../vdom/helpers/index'
export function initEvents (vm: Component) {
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
let target: any
function add (event, fn) {
target.$on(event, fn)
}
function remove (event, fn) {
target.$off(event, fn)
}
function createOnceHandler (event, fn) {
const _target = target
return function onceHandler () {
const res = fn.apply(null, arguments)
if (res !== null) {
_target.$off(event, onceHandler)
}
}
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
//如果event是一个数组就循环调用$on
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
//如果_events[event]不存在,就让_event[event] = [];然后将fn方法push到_event_[event]数组中
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// specific event
const cbs = vm._events[event]
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null
return vm
}
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
if (process.env.NODE_ENV !== 'production') {
const lowerCaseEvent = event.toLowerCase()
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
`Event "${lowerCaseEvent}" is emitted in component ` +
`${formatComponentName(vm)} but the handler is registered for "${event}". ` +
`Note that HTML attributes are case-insensitive and you cannot use ` +
`v-on to listen to camelCase events when using in-DOM templates. ` +
`You should probably use "${hyphenate(event)}" instead of "${event}".`
)
}
}
//获取on 中_events[event]数组,即存放的方法集合
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
//将$emit("eventType", ...其他参数)其他参数转换为数组
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
//循环一次执行cbs[i],并导入args参数
for (let i = 0, l = cbs.length; i < l; i++) {
//invokeWithErrorHandling 内部逻辑
//执行cbs[i]函数,并使该方法里面的this指向vm(当前组件)
//如果args存在的话使用 cbs[i].apply(context, args);
//如果args不存在handler.call(context) 直接执行handler,this指向vm
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}
}
invokeWithErrorHandling 如下
export function invokeWithErrorHandling (
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
if (res && !res._isVue && isPromise(res) && !res._handled) {
res.catch(e => handleError(e, vm, info + ` (Promise/async)`))
// issue #9511
// avoid catch triggering multiple times when nested calls
res._handled = true
}
} catch (e) {
handleError(e, vm, info)
}
return res
}