本文已参与「新人创作礼」活动,一起开启掘金创作之路。
事件特性我们在Vue的日常开发中经常使用,但是你了解Vue中事件的原理吗?本系列我们从源码角度,来分析Vue event的原理。
本节我们从event的编译开始吧。
event编译之parse
在之前的过程中我们分析了编译流程,其中parse流程第一步会扫描开始标签,将开始标签上的所有属性存储在match
对象的attr
属性数组中,然后生成初始的AST
树,解析attr
数组属性,丰富AST
树上的内容(不清楚可以点击这里)。当处理到事件的属性解析时,会进入processAttrs
函数,接下来我们来分析event
的编译的parse流程:
解析事件修饰符
event
的属性解析会进入processAttrs
函数:
export const dirRE = /^v-|^@|^:/
export const bindRE = /^:|^v-bind:/
export const onRE = /^@|^v-on:/
const modifierRE = /\.[^.]+/g
function processAttrs (el) {
// 拿到match对象的attrsList(存放扫描开始标签后各种属性的地方)
const list = el.attrsList
let i, l, name, rawName, value, modifiers, isProp
// 遍历list数组
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name
value = list[i].value
// 匹配(v-)(@)(:)
if (dirRE.test(name)) {
// 匹配上了
// mark element as dynamic
// 标记元素节点为动态节点
el.hasBindings = true
// modifiers
// 解析修饰符
modifiers = parseModifiers(name)
if (modifiers) {
// 存在修饰符,将name后面所有的修饰符去掉
name = name.replace(modifierRE, '')
}
// 匹配(:)(v-bind)
if (bindRE.test(name)) { // v-bind
......
} else if (onRE.test(name)) { // v-on
// 匹配(v-on)(@),去掉(v-on)(@)前缀
name = name.replace(onRE, '')
// 执行addHandler函数
addHandler(el, name, value, modifiers, false, warn)
} else { // normal directives // 普通指令
......
}
} else {
......
}
}
}
const modifierRE = /\.[^.]+/g
// 匹配.
function parseModifiers (name: string): Object | void {
// 修饰符解析称为数组
const match = name.match(modifierRE)
// 数组存在的话,遍历数组,(例如含有修饰符native,则将ret.native = true),返回对象ret
if (match) {
const ret = {}
match.forEach(m => { ret[m.slice(1)] = true })
return ret
}
}
processAttrs
函数的整体逻辑就是解析事件的修饰符,通过parseModifiers
函数将修饰符全部存取进对象modifiers
中,处理完成事件的修饰符后,执行addHandler
函数,并将解析后的修饰符对象modifiers
当做参数传入。
addHandler函数
export function addHandler (
el: ASTElement,
name: string,
value: string,
modifiers: ?ASTModifiers,
important?: boolean,
warn?: Function
) {
modifiers = modifiers || emptyObject
......
// check capture modifier
if (modifiers.capture) {
// 删除capture属性,name前添加'!'
delete modifiers.capture
name = '!' + name // mark the event as captured
}
if (modifiers.once) {
// 删除once属性,name前添加'~'
delete modifiers.once
name = '~' + name // mark the event as once
}
/* istanbul ignore if */
if (modifiers.passive) {
// 删除passive属性,name前添加'&'
delete modifiers.passive
name = '&' + name // mark the event as passive
}
// 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 (name === 'click') {
// 普通的click
if (modifiers.right) {
// 修饰符有right属性,名称改写为contextmenu
name = 'contextmenu'
delete modifiers.right
} else if (modifiers.middle) {
// 修饰符有middle属性,名称改写为mouseup
name = 'mouseup'
}
}
let events
if (modifiers.native) {
// 修饰符有native属性,events赋值为nativeEvents
delete modifiers.native
events = el.nativeEvents || (el.nativeEvents = {})
} else {
// 修饰符没有native属性,events赋值为events
events = el.events || (el.events = {})
}
const newHandler: any = {
// value是当前事件对应的表达式
value: value.trim()
}
if (modifiers !== emptyObject) {
// 如果modifiers还不是空对象
newHandler.modifiers = modifiers
}
// 取出这个handles表达式
const handlers = events[name]
/* istanbul ignore if */
// 如果handlers是数组
if (Array.isArray(handlers)) {
// important是false,是数组直接将新值push进入当前数组
important ? handlers.unshift(newHandler) : handlers.push(newHandler)
} else if (handlers) {
// handles存在但不是数组
// important是false,生成新数组,将之前的handles与新生成的组成一个数组
events[name] = important ? [newHandler, handlers] : [handlers, newHandler]
} else {
// 直接将newHandler赋值给event
events[name] = newHandler
}
// 当前节点的plain赋值为false
el.plain = false
}
addHandler
函数根据事件的修饰符,对event
做不同的处理,最终在当前的AST element
节点上添加events
对象,将处理后的handler
函数丰富在events[name]
中。
小结
我们清楚了在模板上定义event,经过编译后最终会在AST树上生成events(或nativeEvents)属性,下一节我们继续分析event编译的codegen过程。