Vue中的事件分两类,一类是原生事件,另一类是自定义事件
原生事件使用的地方,一个是直接定义在元素上的;另一个是定义在组件上,定义在组件上的原生事件需要加上native的修饰符,例如@click.native自定义事件使用的地方,自定义事件只会使用在组件上,可以在组件上声明@hello,然后在组件内使用 $emit("hello)触发,也可以直接在组件的生命周期方法中使用$.on进行订阅事件,$emit和$on是配套的,不能跨组件使用。
下面从源代码来分析,在compiler/parser/index.js,中通过processAttr进行对获取的属性进行处理,当匹配的属性是以@或者v-on开头的,就按照事件进行处理。源代码如下
function processAttrs (el) {
// 代码省略
if (bindRE.test(name)) { // v-bind
// 代码省略
} else if (onRE.test(name)) { // v-on
name = name.replace(onRE, '')
isDynamic = dynamicArgRE.test(name)
if (isDynamic) {
name = name.slice(1, -1)
}
addHandler(el, name, value, modifiers, false, warn, list[i], isDynamic)
} else { // normal directives
// 代码省略
}
}
在addHandler会把@/v-on所对应的属性值添加到元素的自定义事件名或者原生事件名对应的数组中
export function addHandler (
el: ASTElement,
name: string,
value: string,
modifiers: ?ASTModifiers,
important?: boolean,
warn?: ?Function,
range?: Range,
dynamic?: boolean
) {
modifiers = modifiers || emptyObject
// warn prevent and passive modifier
/* istanbul ignore if */
if (
process.env.NODE_ENV !== 'production' && warn &&
modifiers.prevent && modifiers.passive
) {
warn(
'passive and prevent can\'t be used together. ' +
'Passive handler can\'t prevent default event.',
range
)
}
// normalize click.right and click.middle since they don't actually fire
// this is technically browser-specific, but at least for now browsers are
// the only target envs that have right/middle clicks.
if (modifiers.right) {
if (dynamic) {
name = `(${name})==='click'?'contextmenu':(${name})`
} else if (name === 'click') {
name = 'contextmenu'
delete modifiers.right
}
} else if (modifiers.middle) {
if (dynamic) {
name = `(${name})==='click'?'mouseup':(${name})`
} else if (name === 'click') {
name = 'mouseup'
}
}
// check capture modifier
if (modifiers.capture) {
delete modifiers.capture
name = prependModifierMarker('!', name, dynamic)
}
if (modifiers.once) {
delete modifiers.once
name = prependModifierMarker('~', name, dynamic)
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive
name = prependModifierMarker('&', name, dynamic)
}
let events
if (modifiers.native) {
delete modifiers.native
events = el.nativeEvents || (el.nativeEvents = {})
} else {
events = el.events || (el.events = {})
}
const newHandler: any = rangeSetItem({ value: value.trim(), dynamic }, range)
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers
}
const handlers = events[name]
/* istanbul ignore if */
if (Array.isArray(handlers)) {
important ? handlers.unshift(newHandler) : handlers.push(newHandler)
} else if (handlers) {
events[name] = important ? [newHandler, handlers] : [handlers, newHandler]
} else {
events[name] = newHandler
}
el.plain = false
}
上面的代码是生成抽象语法树中事件的相关代码,接下来是生成可执行代码的字符串相关码。genData中会生成nativeOn: { 事件名: 对应的代码 …}和on: { 事件名: 对应的代码…},genHandlers中的代码可以查看Vue中的源代码
export function genData (el: ASTElement, state: CodegenState): string {
let data = '{'
// 代码省略
if (el.events) {
data += `${genHandlers(el.events, false)},`
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true)},`
}
// 代码省略
return data
}
上面生成的代码会在vm.$mount的时候执行
下面是Vue初始化的时候,事件如何执行的,自定义事件是在initEvents方法中执行的,只有组件才会存在_parentListeners,_parentListeners就是genData中的on:存放的事件,里面的事件会执行Vue.prototype.$on的方法,把对应的事件名和事件方法存在vm._events,Vue.prototype.emit在vm._events找到事件名,执行对应的方法
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)
}
}
上面代码代码是自定义事件的处理,下面分析一下原生事件的处理,主要的代码在src/plaforms/web/runtime/modules/events.js里面,是对应真正的DOM绑定事件
function updateDOMListeners (oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
const on = vnode.data.on || {}
const oldOn = oldVnode.data.on || {}
// vnode is empty when removing all listeners,
// and use old vnode dom element
target = vnode.elm || oldVnode.elm
normalizeEvents(on)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}
执行DOM绑定事件是在node的hook中执行的,代码在src/core/vdom/patch.js中。