源码分析:Vue 事件原理(从源码角度带你分析)

544 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

事件特性我们在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过程。