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

321 阅读2分钟

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

上一节我们介绍了event的parse过程(点击这里查看),这一节我们来分析event的gencode的过程,关于genCode的大致流程,我之前的文章已经介绍过了(点击这里查看),接下来我们开始吧。

event编译之genCode

在codegen流程中会遍历AST树生成字符串代码,这个过程中,会执行genElement解析标签上定义的不同属性:

export function genElement (el: ASTElement, state: CodegenState): string {
  if (el.staticRoot && !el.staticProcessed) {
    ......
  } else {
    // component or element
    let code
    if (el.component) {
      code = genComponent(el.component, el, state)
    } else {
      // el.plain是false,普通元素执行genData,事件处理也会进入这个函数
      const data = el.plain ? undefined : genData(el, state)

      ......
    }
    ......
    return code
  }
}

当遇到event事件的时候,会执行genData方法:

genData

export function genData (el: ASTElement, state: CodegenState): string {
  let data = '{'

  ......
  // event handlers
  if (el.events) {
    // AST element对象上存在events,执行genHandlers第二个参数是false
    data += `${genHandlers(el.events, false, state.warn)},`
  }
  if (el.nativeEvents) {
    // AST element对象上存在nativeEvents,执行genHandlers第二个参数是true
    data += `${genHandlers(el.nativeEvents, true, state.warn)},`
  }
  ......
  return data
}

当遇到AST树中存在events或者nativeEvents属性,会执行genHandlers方法生成字符串代码。

export function genHandlers (
  events: ASTElementHandlers,
  isNative: boolean,
  warn: Function
): string {
  // 根据传入的isNative参数拼接结果字符串
  let res = isNative ? 'nativeOn:{' : 'on:{'
  // 遍历events,拿到每一个事件名
  for (const name in events) {
    // 执行genHandler方法拼接字符串
    res += `"${name}":${genHandler(name, events[name])},`
  }
  // 截断最后一个大括号,拼接大括号
  return res.slice(0, -1) + '}'
}

执行genHandlers之后:

如果是events对象,如拼接非native click事件的code的结果为:

on:{click:${genHandler(name, events[name])},

如果是nativeEvents对象,如拼接native click事件的code的结果为:

nativeOn:{click:${genHandler(name, events[name])},

genHandler

const modifierCode: { [key: string]: string } = {
  stop: '$event.stopPropagation();',
  prevent: '$event.preventDefault();',
  self: genGuard(`$event.target !== $event.currentTarget`),
  ctrl: genGuard(`!$event.ctrlKey`),
  shift: genGuard(`!$event.shiftKey`),
  alt: genGuard(`!$event.altKey`),
  meta: genGuard(`!$event.metaKey`),
  left: genGuard(`'button' in $event && $event.button !== 0`),
  middle: genGuard(`'button' in $event && $event.button !== 1`),
  right: genGuard(`'button' in $event && $event.button !== 2`)
}

function genHandler (
  name: string,
  handler: ASTElementHandler | Array<ASTElementHandler>
): string {
  if (!handler) {
    return 'function(){}'
  }

  // 之前生成的handler如果是数组,遍历数组执行genHandler再拼接
  if (Array.isArray(handler)) {
    return `[${handler.map(handler => genHandler(name, handler)).join(',')}]`
  }

  // 匹配代码路径, 匹配不上情况,例 handleClick($event)
  const isMethodPath = simplePathRE.test(handler.value)  // 匹配函数名路径,例 handleClick
  const isFunctionExpression = fnExpRE.test(handler.value)  // 匹配函数路径,例 function(){}

  if (!handler.modifiers) {
    // 没有其他修饰符,进入该逻辑
    if (isMethodPath || isFunctionExpression) {
      // 匹配到了路径,直接返回value值
      return handler.value
    }
    /* istanbul ignore if */
    if (__WEEX__ && handler.params) {
      return genWeexHandler(handler.params, handler.value)
    }
    // 没匹配到路径,返回下面这种格式 如我们定义的value是handleClick($event),
    // 返回`function($event){handleClick($event)}`
    return `function($event){${handler.value}}` // inline statement
  } else {
    // 有修饰符进入该逻辑
    let code = ''
    let genModifierCode = ''
    const keys = []
    // 遍历修饰符
    for (const key in handler.modifiers) {
      // 在modifierCode对象中查找,匹配上赋值给genModifierCode
      // 例如我们定义了修饰符 prevent
      if (modifierCode[key]) {
        // prevent生成的代码: genModifierCode = '$event.preventDefault();',
        genModifierCode += modifierCode[key]
        // left/right
        if (keyCodes[key]) {
          keys.push(key)
        }
      } else if (key === 'exact') {
        // 没匹配上modifierCode,修饰符是exact,进入该逻辑处理
        const modifiers: ASTModifiers = (handler.modifiers: any)
        genModifierCode += genGuard(
          ['ctrl', 'shift', 'alt', 'meta']
            .filter(keyModifier => !modifiers[keyModifier])
            .map(keyModifier => `$event.${keyModifier}Key`)
            .join('||')
        )
      } else {
        // 如果没匹配上以上逻辑,将key push进入keys
        keys.push(key)
      }
    }
    // keys有值,执行genKeyFilter,这个函数就是处理一些键盘事件
    if (keys.length) {
      code += genKeyFilter(keys)
    }
    // Make sure modifiers like prevent and stop get executed after key filtering
    // 经过以上处理后genModifierCode仍存在,将该字符串拼接
    // 这里genModifierCode = '$event.preventDefault();',
    if (genModifierCode) {
      code += genModifierCode
    }
    // 判断生成handlerCode
    const handlerCode = isMethodPath
      ? `return ${handler.value}($event)`
      : isFunctionExpression
        ? `return (${handler.value})($event)`
        : handler.value
    /* istanbul ignore if */
    if (__WEEX__ && handler.params) {
      return genWeexHandler(handler.params, code + handlerCode)
    }
    // 返回下面的拼接字符串
    // `function($event){$event.preventDefault();return handleClick($event)`
    return `function($event){${code}${handlerCode}}`
  }
}

genHandler是最终的代码生成逻辑,首先会通过正则字符串匹配我们定义的事件的格式,再根据是否存在修饰符进入不同的逻辑:

1.没有修饰符的时候:

  • 匹配上函数名:
eg: @click="handleClick"
genCode: `handleClick`
  • 匹配上函数:
eg: @click="function() {return ''}"
genCode: `function() {return ''}`
  • 没匹配上以上格式
eg: @click="handleClick($event)"
genCode: `function($event){handleClick($event)}`

2.有修饰符的时候

eg: @click.prevent="handleClick"
genCode: ``function($event){$event.preventDefault();handleClick($event)`

小结

event的genCode会根据event或nativeEvent类型生成不同的对象,结合定义的修饰符,执行genHandler函数,根据匹配的事件对象中的value内容,生成不同的代码字符串。

下一节我们会继续分析event的执行过程。