vue源码分析-事件机制

这个系列讲到这里,Vue基本核心的东西已经分析完,但是Vue之所以强大,离不开它提供给用户的一些实用功能,开发者可以更偏向于业务逻辑而非基本功能的实现。例如,在日常开发中,我们将@click=***用得飞起,但是我们是否思考,Vue如何在后面为我们的模板做事件相关的处理,并且我们经常利用组件的自定义事件去实现父子间的通信,那这个事件和和原生dom事件又有不同的地方吗,能够实现通信的原理又是什么,带着疑惑,我们深入源码展开分析。

9.1. 模板编译

Vue在挂载实例前,有相当多的工作是进行模板的编译,将template模板进行编译,解析成AST树,再转换成render函数,而有了render函数后才会进入实例挂载过程。对于事件而言,我们经常使用v-on或者@在模板上绑定事件。因此对事件的第一步处理,就是在编译阶段对事件指令做收集处理。

从一个简单的用法分析编译阶段收集的信息:

<div id="app">
    <div v-on:click.stop="doThis">点击</div>
    <span>{
  {count}}</span>
</div>
<script>
var vm = new Vue({
         el: '#app',    data() {
             return {
                 count: 1
        }    },    methods: {
             doThis() {
                 ++this.count
        }    }})
</script>

我们之前在将模板编译的时候大致说过编译的流程,模板编译的入口是在var ast = parse(template.trim(), options);中,parse通过拆分模板字符串,将其解析为一个AST树,其中对于属性的处理,在processAttr中,由于分支较多,我们只分析例子中的流程。

var dirRE = /^v-|^@|^:/;

function processAttrs (el) {
   
    var list = el.attrsList;
    var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
    for (i = 0, l = list.length; i < l; i++) {
   
      name = rawName = list[i].name; // v-on:click
      value = list[i].value; // doThis
      if (dirRE.test(name)) {
    // 匹配v-或者@开头的指令
        el.hasBindings = true;
        modifiers = parseModifiers(name.replace(dirRE, ''));// parseModifiers('on:click')
        if (modifiers) {
   
          name = name.replace(modifierRE, '');
        }
        if (bindRE.test(name)) {
    // v-bind分支
          // ...留到v-bind指令时分析
        } else if (onRE.test(name)) {
    // v-on分支
          name = name.replace(onRE, ''); // 拿到真正的事件click
          isDynamic = dynamicArgRE.test(name);// 动态事件绑定
          if (isDynamic) {
   
            name = name.slice(1, -1);
          }
          addHandler(el, name, value, modifiers, false, warn$2, list[i], isDynamic);
        } else {
    // normal directives
         // 其他指令相关逻辑
      } else {
   }
    }
  }

processAttrs的逻辑虽然较多,但是理解起来较为简单,var dirRE = /^v-|^@|^:/;是匹配事件相关的正则,命中匹配的记过会得到事件指令相关内容,包括事件本身,事件回调以及事件修饰符。最终通过addHandler方法,为AST树添加事件相关的属性。而addHandler还有一个重要功能是对事件修饰符进行特殊处理。

// el是当前解析的AST树
function addHandler (el,name,value,modifiers,important,warn,range,dynamic) {
   
    modifiers = modifiers || emptyObject;
    // passive 和 prevent不能同时使用,可以参照官方文档说明
    if (
      warn &&
      modifiers.prevent && modifiers.passive
    ) {
   
      warn(
        'passive and prevent can\'t be used together. ' +
        'Passive handler can\'t prevent default event.',
        range
      );
    }
    // 这部分的逻辑会对特殊的修饰符做字符串拼接的处理,以备后续的使用
    if (modifiers.right) {
   
      if (dynamic) {
   
        name = "(" + name + ")==='click'?'contextmenu':(" + name + ")";
      } else if (name === 'click') {
   
        name = 'contextmenu';
        delete modifiers.right;
      }
    } else if (modifiers.middle) {
   
      if (dynamic) {
   
        name = "(" + name + ")==='click'?'mouseup':(" + name + ")";
      } else if (name === 'click') {
   
        name = 'mouseup';
      }
    }
    if (modifiers.capture) {
   
      delete modifiers.capture;
      name = prependModifierMarker('!', name, dynamic);
    }
    if (modifiers.once) {
   
      delete modifiers.once;
      name = prependModifierMarker('~', name, dynamic);
    }
    /* istanbul ignore if */
    if (modifiers.passive) {
   
      delete modifiers.passive;
      name = prependModifierMarker('&', name, dynamic);
    }
    // events 用来记录绑定的事件
    var events;
    if (modifiers.native) {
   
      delete modifiers.native;
      events = el.nativeEvents || (el.nativeEvents = {
   });
    } else {
   
      events = el.events || (el.events = {
   });
    }

    var newHandler = rangeSetItem({
    value: value.trim(), dynamic: dynamic }, range);
    if (modifiers !== emptyObject) {
   
      newHandler.modifiers = modifiers;
    }

    var handlers = events[name];
    /* istanbul ignore if */
    // 绑定的事件可以多个,回调也可以多个,最终会合并到数组中
    if (Array.isArray(handlers)) {
   
      important ? handlers.unshift(newHandler) : handlers.push(newHandler);
    } else if (handlers) {
   
      events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
    } else {
   
      events[name] = newHandler;
    }
    el.plain = false;
  }

修饰符的处理会改变最终字符串的拼接结果,我们看最终转换的AST树:

9.2. 代码生成

模板编译的最后一步是根据解析完的AST树生成对应平台的渲染函数,也就是render函数的生成过程, 对应var code = generate(ast, options);

function generate (ast,options) {
   
    var state = new CodegenState(options);
    var code = ast ? genElement(ast, state) : '_c("div")';
    return {
   
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值