• 9.1. 模板编译

    9.1. 模板编译

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

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

    1. <div id="app">
    2. <div v-on:click.stop="doThis">点击</div>
    3. <span>{{count}}</span>
    4. </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.1. 模板编译 - 图1