事件特性我们在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 = /\.[^.]+/gfunction 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过程。
原文:https://juejin.cn/post/7096277031788216351