一、事件类型
1、原生DOM事件
- 普通DOM元素或组件.native修饰的事件;
- 在patch调用createElm创建元素时利用
target.addEventListener()
添加事件
2、自定义事件
- 组件自定义事件
- 在_init调用initEvents初始化事件时,调用 Vue 原型上的
$on
将父组件绑定的自定义事件绑定到实例的_events属性上,并在方法中使用$emit
触发该事件
二、源码解析
- 事件在模板编译阶段以属性
on
的形式存在,在真实节点渲染阶段会根据事件属性去绑定相关的事件 - 原生事件:
createElm
时调用create hook调用钩子函数updateDomListeners
传递的 add 调用addEventListener
添加事件 - 自定义事件:initEvents传递父组件绑定事件,updateListeners时调用传递的 add 内部的
$on
添加事件 - 在解析到render函数后,事件都在on上
1、原生事件
patch执行createElm时,调用updateDomListeners
// 调用createChildren遍历子节点创建对应的DOM节点
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
// 执行create钩子函数(directives events等的create hook)
// updateAttrs
// updateClass
// updateDomListeners 普通的原生事件
// updateDomProps
// updateStyle
// updateDirectives
invokeCreateHooks(vnode, insertedVnodeQueue)
}
updateDomListeners
在src/platforms/web/runtime/patch.js
创建平台相关patch
时引入
import * as nodeOps from 'web/runtime/node-ops'
import { createPatchFunction } from 'core/vdom/patch'
import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'
// the directive module should be applied last, after all
// built-in modules have been applied.
const modules = platformModules.concat(baseModules)
// 引入基础modules和平台相关modules
// 基础directives ref
export const patch: Function = createPatchFunction({ nodeOps, modules })
createPatchFunction
创建patch
时在cbs中保存所以modules的hook
let i, j
const cbs = {}
const { modules, nodeOps } = backend
for (i = 0; i < hooks.length; ++i) {
cbs[hooks[i]] = []
for (j = 0; j < modules.length; ++j) {
if (isDef(modules[j][hooks[i]])) {
cbs[hooks[i]].push(modules[j][hooks[i]])
}
}
}
updateDOMListeners
取出新旧dom节点的on事件,并兼任处理IE下不支持input事件,修改为change事件后,调用updateListeners
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 || {}
target = vnode.elm
normalizeEvents(on)
updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
target = undefined
}
遍历on事件对新节点事件绑定注册事件,对旧节点移除事件监听;
add函数调用原生的addEventListener在真正的 DOM上绑定事件。
export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
...
// 执行真正注册事件的执行函数
add(event.name, cur, event.capture, event.passive, event.params)
} else if (cur !== old) {
old.fns = cur
on[name] = old
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name)
remove(event.name, oldOn[name], event.capture)
}
}
}
target.addEventListener(
name,
handler,
supportsPassive
? { capture, passive }
: capture
)
2、自定义事件
- 子组件通过vm.$emit向父组件派发事件,父组件通过v-on:(event)绑定的事件方法接受信息并处理回调;
initInternalComponent时,保存上级组件传递的数据:
options._parentVnode
是在调用createComponentInstanceForVnode创建组件实例时保存的父级组件vnode
options.parent
是在调用createComponentInstanceForVnode创建组件实例时保存的父级组件实例
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
const opts = vm.$options = Object.create(vm.constructor.options)
// doing this because it's faster than dynamic enumeration.
const parentVnode = options._parentVnode
opts.parent = options.parent
opts._parentVnode = parentVnode
const vnodeComponentOptions = parentVnode.componentOptions
opts.propsData = vnodeComponentOptions.propsData
opts._parentListeners = vnodeComponentOptions.listeners
opts._renderChildren = vnodeComponentOptions.children
opts._componentTag = vnodeComponentOptions.tag
if (options.render) {
opts.render = options.render
opts.staticRenderFns = options.staticRenderFns
}
}
在_init
方法中调用initEvents
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
src/core/instance/events.js
清空数据,初始化连接父级事件;
创建私有的事件对象和是否有事件钩子的标志两个属性,
然后根据父级是否有事件处理器来决定是否更新当前实例的事件监听器
vm._events = Object.create(null)
vm._hasHookEvent = false
// init parent attached events
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, createOnceHandler, vm)
target = undefined
}
只传递了on和vm,代码简化后,add方法调用的$on
export function updateListeners (
on: Object,
oldOn: Object,
add: Function,
remove: Function,
createOnceHandler: Function,
vm: Component
) {
let name, def, cur, old, event
for (name in on) {
def = cur = on[name]
old = oldOn[name]
event = normalizeEvent(name)
...
add(event.name, cur, event.capture, event.passive, event.params)
...
}
}
event是数组则遍历执行$on;
否则在initEvents时声明的_events
对象的该事件数组中添加该事件
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
(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
}
自定义事件触发$emit
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
const info = `event handler for "${event}"`
for (let i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info)
}
}
return vm
}