一拼苍、事件類型
1骤坐、原生DOM事件
- 普通DOM元素或組件.native修飾的事件著洼;
- 在patch調(diào)用createElm創(chuàng)建元素時(shí)利用
target.addEventListener()
添加事件
2捆交、自定義事件
- 組件自定義事件
- 在_init調(diào)用initEvents初始化事件時(shí)阳距,調(diào)用 Vue 原型上的
$on
將父組件綁定的自定義事件綁定到實(shí)例的_events屬性上,并在方法中使用$emit
觸發(fā)該事件
二、源碼解析
- 事件在模板編譯階段以屬性
on
的形式存在含懊,在真實(shí)節(jié)點(diǎn)渲染階段會(huì)根據(jù)事件屬性去綁定相關(guān)的事件 - 原生事件:
createElm
時(shí)調(diào)用create hook調(diào)用鉤子函數(shù)updateDomListeners
傳遞的 add 調(diào)用addEventListener
添加事件 - 自定義事件:initEvents傳遞父組件綁定事件身冬,updateListeners時(shí)調(diào)用傳遞的 add 內(nèi)部的
$on
添加事件 - 在解析到render函數(shù)后,事件都在on上
1岔乔、原生事件
patch執(zhí)行createElm時(shí)酥筝,調(diào)用updateDomListeners
// 調(diào)用createChildren遍歷子節(jié)點(diǎn)創(chuàng)建對(duì)應(yīng)的DOM節(jié)點(diǎn)
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
// 執(zhí)行create鉤子函數(shù)(directives events等的create hook)
// updateAttrs
// updateClass
// updateDomListeners 普通的原生事件
// updateDomProps
// updateStyle
// updateDirectives
invokeCreateHooks(vnode, insertedVnodeQueue)
}
updateDomListeners
在src/platforms/web/runtime/patch.js
創(chuàng)建平臺(tái)相關(guān)patch
時(shí)引入
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)
// 引入基礎(chǔ)modules和平臺(tái)相關(guān)modules
// 基礎(chǔ)directives ref
export const patch: Function = createPatchFunction({ nodeOps, modules })
createPatchFunction
創(chuàng)建patch
時(shí)在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節(jié)點(diǎn)的on事件,并兼任處理IE下不支持input事件雏门,修改為change事件后嘿歌,調(diào)用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事件對(duì)新節(jié)點(diǎn)事件綁定注冊事件,對(duì)舊節(jié)點(diǎn)移除事件監(jiān)聽茁影;
add函數(shù)調(diào)用原生的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) {
...
// 執(zhí)行真正注冊事件的執(zhí)行函數(shù)
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向父組件派發(fā)事件呼胚,父組件通過v-on:(event)綁定的事件方法接受信息并處理回調(diào)茄唐;
initInternalComponent時(shí)息裸,保存上級(jí)組件傳遞的數(shù)據(jù):
options._parentVnode
是在調(diào)用createComponentInstanceForVnode創(chuàng)建組件實(shí)例時(shí)保存的父級(jí)組件vnode
options.parent
是在調(diào)用createComponentInstanceForVnode創(chuàng)建組件實(shí)例時(shí)保存的父級(jí)組件實(shí)例
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
方法中調(diào)用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
清空數(shù)據(jù)蝇更,初始化連接父級(jí)事件;
創(chuàng)建私有的事件對(duì)象和是否有事件鉤子的標(biāo)志兩個(gè)屬性,
然后根據(jù)父級(jí)是否有事件處理器來決定是否更新當(dāng)前實(shí)例的事件監(jiān)聽器
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方法調(diào)用的$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是數(shù)組則遍歷執(zhí)行$on年扩;
否則在initEvents時(shí)聲明的_events
對(duì)象的該事件數(shù)組中添加該事件
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
}
自定義事件觸發(fā)$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
}