Vue源码分析(6)--编译过程分析(1)

前言

本文是vue2.x源码分析的第六篇,主要讲解编译compile过程!

调用方式

var compiled = compile(template, options);

1 分析compile

//tips:请结合断点调试,该函数位于闭包createCompiler中,有的变量是在上层函数中定义的
function compile (template, options) {
    var finalOptions = Object.create(baseOptions);
    var errors = [];
    var tips = [];
    finalOptions.warn = function (msg, tip$$1) {
      (tip$$1 ? tips : errors).push(msg);
    };
    if (options) {
      //合并自定义modules
      if (options.modules) {
        finalOptions.modules = (baseOptions.modules || []).concat(options.modules);
      }
      //合并自定义directives
      if (options.directives) {
        finalOptions.directives = extend(
          Object.create(baseOptions.directives),
          options.directives
        );
      }
      // copy other options
      for (var key in options) {
        if (key !== 'modules' && key !== 'directives') {
          finalOptions[key] = options[key];
        }
      }
    }
    /*以上都是处理finalOptions,到这里finalOptions如下:
    {
        delimiters:undefined,
        shouldDecodeNewlines:false,
        warn:function (msg, tip$$1),
        __proto__:Object
    }
    这个__proto__指向一个预先定义好的baseOptions对象,该对象长这样:
        var baseOptions = {
          expectHTML: true,
          modules: modules$1,//modules$1=[klass$1,style$1]
          directives: directives$1, //这里预先定义了html,text,model三个指令
          isPreTag: isPreTag,
          isUnaryTag: isUnaryTag,
          mustUseProp: mustUseProp,
          canBeLeftOpenTag: canBeLeftOpenTag,
          isReservedTag: isReservedTag,
          getTagNamespace: getTagNamespace,
          staticKeys: genStaticKeys(modules$1)
        };
    */
    var compiled = baseCompile(template, finalOptions); //主要函数
    {
      errors.push.apply(errors, detectErrors(compiled.ast));
    }
    compiled.errors = errors;
    compiled.tips = tips;
    return compiled
  }

来看看baseCompile(template, finalOptions)

function baseCompile (template,options) {
  var ast = parse(template.trim(), options); //主要函数1
  optimize(ast, options);
  var code = generate(ast, options);         //主要函数2
  return {
    ast: ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
}

2 分析 parse(template.trim(), options);

//主要是调用parseHTML(html, options)解析html,返回结果ast是含有如下属性的对象
    // attrs:Array
    // attrsList:Array
    // attrsMap:Object
    // children:Array
    // parent:undefined
    // plain:false
    // static:false
    // staticRoot:false
    // tag:"div"
    // type:1
    // __proto__:Object
function parse (template,options) {
  warn$2 = options.warn || baseWarn;
  platformGetTagNamespace = options.getTagNamespace || no;
  platformMustUseProp = options.mustUseProp || no;
  platformIsPreTag = options.isPreTag || no;
  //这个pluckModuleFunction函数作用就是从options.modules中取出key为'preTransformNode'的值
  preTransforms = pluckModuleFunction(options.modules, 'preTransformNode');
  transforms = pluckModuleFunction(options.modules, 'transformNode');
  postTransforms = pluckModuleFunction(options.modules, 'postTransformNode');
  delimiters = options.delimiters;
  var stack = [];
  var preserveWhitespace = options.preserveWhitespace !== false;
  var root;  //作为结果返回
  var currentParent;
  var inVPre = false;
  var inPre = false;
  var warned = false;
  function warnOnce (msg) {
    if (!warned) {
      warned = true;
      warn$2(msg);
    }
  }
  function endPre (element) {
    // check pre state
    if (element.pre) {
      inVPre = false;
    }
    if (platformIsPreTag(element.tag)) {
      inPre = false;
    }
  }
  //parseHTML第二个参数里有很重要的三个函数:start,end,chars
  parseHTML(template, {
    warn: warn$2,
    expectHTML: options.expectHTML,
    isUnaryTag: options.isUnaryTag,
    canBeLeftOpenTag: options.canBeLeftOpenTag,
    shouldDecodeNewlines: options.shouldDecodeNewlines,
    //start和end函数负责构建节点树
    start: function start (tag, attrs, unary) {
      // check namespace.
      // inherit parent ns if there is one
      var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag);
      // handle IE svg bug
      /* istanbul ignore if */
      if (isIE && ns === 'svg') {
        attrs = guardIESVGBug(attrs);
      }
      var element = { //节点
        type: 1,
        tag: tag,
        attrsList: attrs,
        attrsMap: makeAttrsMap(attrs),
        parent: currentParent,
        children: []
      };
      if (ns) {
        element.ns = ns;
      }
      //不处理style和script标签
      if (isForbiddenTag(element) && !isServerRendering()) {
        element.forbidden = true;
        "development" !== 'production' && warn$2(
          'Templates should only be responsible for mapping the state to the ' +
          'UI. Avoid placing tags with side-effects in your templates, such as ' +
          "<" + tag + ">" + ', as they will not be parsed.'
        );
      }
      // 猜测:html如果用了其他的模板,如ejs等需要先转换
      // apply pre-transforms
      for (var i = 0; i < preTransforms.length; i++) {
        preTransforms[i](element, options);
      }
      //处理v-pre指令
      if (!inVPre) {
        processPre(element);
        if (element.pre) {
          inVPre = true;
        }
      }
      if (platformIsPreTag(element.tag)) {
        inPre = true;
      }
      //如果含有v-pre指令,则直接调用processRawAttrs(element);处理原始属性
      if (inVPre) {
        processRawAttrs(element);
      } else {
        processFor(element);//处理v-for指令,会将v-for='xx'替换成其他字符串
        processIf(element);//处理v-if指令
        processOnce(element);//处理v-once指令
        processKey(element);//处理key
        // determine whether this is a plain element after
        // removing structural attributes
        // 移除结构性属性后判断该元素是不是plain元素
        element.plain = !element.key && !attrs.length;
        processRef(element);//处理ref
        processSlot(element);//处理slot
        processComponent(element);//处理component
        for (var i$1 = 0; i$1 < transforms.length; i$1++) {
          transforms[i$1](element, options); //对class和style属性进行处理
        }
        //以上处理了v-for,v-if,v-once,v-pre等指令,但还有其它指令,如v-on,v-bind,
        //以及它们的快捷写法'@:',':',该函数就是处理这些指令以及普通元素,处理的结果就是
        //在element上加了一个attrs属性,存放原始属性
        processAttrs(element);
      }
      function checkRootConstraints (el) {
        {
          if (el.tag === 'slot' || el.tag === 'template') {
            warnOnce(
              "Cannot use <" + (el.tag) + "> as component root element because it may " +
              'contain multiple nodes.'
            );
          }
          if (el.attrsMap.hasOwnProperty('v-for')) {
            warnOnce(
              'Cannot use v-for on stateful component root element because ' +
              'it renders multiple elements.'
            );
          }
        }
      }
      // 经过上述处理后,由于可能有v-if这种会改变树结构的指令,所以需要对结构树
      // 进一步处理,至此第一轮while循环解析完成,接下来就是重复这个过程了
      if (!root) {
        root = element;
        checkRootConstraints(root); //根节点不能是slot/template元素,且不能含有v-for指令
      } else if (!stack.length) {
        // 允许根元素使用 v-if, v-else-if and v-else
        if (root.if && (element.elseif || element.else)) {
          checkRootConstraints(element);
          addIfCondition(root, {
            exp: element.elseif,
            block: element
          });
        } else {
          warnOnce(
            "Component template should contain exactly one root element. " +
            "If you are using v-if on multiple elements, " +
            "use v-else-if to chain them instead."
          );
        }
      }
      if (currentParent && !element.forbidden) {
        if (element.elseif || element.else) {
          processIfConditions(element, currentParent);
        } else if (element.slotScope) { // scoped slot
          currentParent.plain = false;
          var name = element.slotTarget || '"default"';(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element;
        } else {
          currentParent.children.push(element);
          element.parent = currentParent;
        }
      }
      if (!unary) {
        currentParent = element;
        stack.push(element);
      } else {
        endPre(element);
      }
      // apply post-transforms
      for (var i$2 = 0; i$2 < postTransforms.length; i$2++) {
        postTransforms[i$2](element, options);
      }
    },
    end: function end () {
      // 删除尾随空格
      var element = stack[stack.length - 1];
      var lastNode = element.children[element.children.length - 1];
      if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) {
        element.children.pop();
      }
      // pop stack
      stack.length -= 1;
      currentParent = stack[stack.length - 1];
      endPre(element);
    },
    chars: function chars (text) {
      if (!currentParent) {
        {
          if (text === template) {
            warnOnce(
              'Component template requires a root element, rather than just text.'
            );
          } else if ((text = text.trim())) {
            warnOnce(
              ("text \"" + text + "\" outside root element will be ignored.")
            );
          }
        }
        return
      }
      // IE textarea placeholder bug
      /* istanbul ignore if */
      if (isIE &&
          currentParent.tag === 'textarea' &&
          currentParent.attrsMap.placeholder === text) {
        return
      }
      var children = currentParent.children;
      text = inPre || text.trim()
        ? decodeHTMLCached(text)
        // only preserve whitespace if its not right after a starting tag
        : preserveWhitespace && children.length ? ' ' : '';
      if (text) {
        var expression;
        if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) {
          children.push({
            type: 2,
            expression: expression,
            text: text
          });
        } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') {
          children.push({
            type: 3,
            text: text
          });
        }
      }
    }
  });



  return root
}

来看看parseHTML(template,options)

/*解析过程中最重要的函数,因此代码量较大*/
function parseHTML (html, options) {  //将template传给html
  var stack = [];
  var expectHTML = options.expectHTML;
  var isUnaryTag$$1 = options.isUnaryTag || no; //是否是一元标签
  var canBeLeftOpenTag$$1 = options.canBeLeftOpenTag || no;
  var index = 0;
  var last, lastTag;
  while (html) {       //通过while循环一步步处理html,每处理一步就缩短html,直至html为空
    last = html;
    // 不处理script/style/textarea元素
    if (!lastTag || !isPlainTextElement(lastTag)) {
      var textEnd = html.indexOf('<');
      if (textEnd === 0) {
        // 当匹配到Comment,只对html推进,不做其他处理
        if (comment.test(html)) {
          var commentEnd = html.indexOf('-->');
          if (commentEnd >= 0) {
            advance(commentEnd + 3);
            continue
          }
        }
        //当匹配到conditionalComment,同Comment一样处理
        // http://en.wikipedia.org/wiki/Conditional_comment#Downlevel-revealed_conditional_comment
        if (conditionalComment.test(html)) {
          var conditionalEnd = html.indexOf(']>')
          if (conditionalEnd >= 0) {
            advance(conditionalEnd + 2);
            continue
          }
        }
        //当匹配到doctype,同Comment一样处理,/^<!DOCTYPE [^>]+>/i
        var doctypeMatch = html.match(doctype);
        if (doctypeMatch) {
          advance(doctypeMatch[0].length);
          continue
        }
        // 当匹配到end tag,同Comment一样处理,/^<\/((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)[^>]*>/
        var endTagMatch = html.match(endTag);
        if (endTagMatch) {
          var curIndex = index;
          advance(endTagMatch[0].length);
          parseEndTag(endTagMatch[1], curIndex, index);
          continue
        }
        // 除以上四种,就默认以下处理
        var startTagMatch = parseStartTag();
        if (startTagMatch) {
          handleStartTag(startTagMatch);
          continue
        }
      }
      var text = (void 0), rest$1 = (void 0), next = (void 0);
      if (textEnd >= 0) {
        rest$1 = html.slice(textEnd);
        while (
          !endTag.test(rest$1) &&
          !startTagOpen.test(rest$1) &&
          !comment.test(rest$1) &&
          !conditionalComment.test(rest$1)
        ) {
          // < in plain text, be forgiving and treat it as text
          next = rest$1.indexOf('<', 1);
          if (next < 0) { break }
          textEnd += next;
          rest$1 = html.slice(textEnd);
        }
        text = html.substring(0, textEnd);
        advance(textEnd);
      }
      if (textEnd < 0) {
        text = html;
        html = '';
      }
      if (options.chars && text) {
        options.chars(text);
      }
    }
    else {
      var stackedTag = lastTag.toLowerCase();
      var reStackedTag = reCache[stackedTag] || (reCache[stackedTag] = new RegExp('([\\s\\S]*?)(</' + stackedTag + '[^>]*>)', 'i'));
      var endTagLength = 0;
      var rest = html.replace(reStackedTag, function (all, text, endTag) {
        endTagLength = endTag.length;
        if (!isPlainTextElement(stackedTag) && stackedTag !== 'noscript') {
          text = text
            .replace(/<!--([\s\S]*?)-->/g, '$1')
            .replace(/<!\[CDATA\[([\s\S]*?)]]>/g, '$1');
        }
        if (options.chars) {
          options.chars(text);
        }
        return ''
      });
      index += html.length - rest.length;
      html = rest;
      parseEndTag(stackedTag, index - endTagLength, index);
    }
    if (html === last) {
      options.chars && options.chars(html);
      if ("development" !== 'production' && !stack.length && options.warn) {
        options.warn(("Mal-formatted tag at end of template: \"" + html + "\""));
      }
      break
    }
  }
  // Clean up any remaining tags
  parseEndTag();
  function advance (n) {
    index += n;
    html = html.substring(n);
  }
  function parseStartTag () {
    //startTagOpen='/^<((?:[a-zA-Z_][\w\-\.]*\:)?[a-zA-Z_][\w\-\.]*)/'
    var start = html.match(startTagOpen);
    if (start) {
      var match = {
        tagName: start[1],
        attrs: [],
        start: index
      };
      advance(start[0].length);
      var end, attr;
      //开始寻找属性
      //startTagClose='/^\s*(\/?)>/'
      //attribute='/^\s*([^\s"'<>\/=]+)(?:\s*((?:=))\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/'
      while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
        advance(attr[0].length);
        match.attrs.push(attr);
      }
      if (end) {
        match.unarySlash = end[1];//若'/'存在,则赋值给unarySlash
        advance(end[0].length);
        match.end = index;
        return match   //至此,parseStartTag结束,接下来执行handleStartTag(match);match此时长这样
        /*
        attrs:Array(1)
        end:14
        start:0
        tagName:"div"
        unarySlash:""
        __proto__:Object
         */
      }
    }
  }
  function handleStartTag (match) {
    var tagName = match.tagName;
    var unarySlash = match.unarySlash;
    if (expectHTML) {
      if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
        parseEndTag(lastTag);
      }
      if (canBeLeftOpenTag$$1(tagName) && lastTag === tagName) {
        parseEndTag(tagName);
      }
    }
    var unary = isUnaryTag$$1(tagName) || tagName === 'html' && lastTag === 'head' || !!unarySlash;
    var l = match.attrs.length;
    //新建一个attrs属性,遍历match.attrs,使得attrs=[{name:'id',value:'app'}]这种map结构
    var attrs = new Array(l);
    for (var i = 0; i < l; i++) {
      var args = match.attrs[i];
      // hackish work around FF bug https://bugzilla.mozilla.org/show_bug.cgi?id=369778
      if (IS_REGEX_CAPTURING_BROKEN && args[0].indexOf('""') === -1) {
        if (args[3] === '') { delete args[3]; }
        if (args[4] === '') { delete args[4]; }
        if (args[5] === '') { delete args[5]; }
      }
      var value = args[3] || args[4] || args[5] || '';
      attrs[i] = {
        name: args[1],
        value: decodeAttr(
          value,
          options.shouldDecodeNewlines
        )
      };
    }
    if (!unary) {
      stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs });
      lastTag = tagName;
    }
    //这个是最重要的函数,对一些特殊的属性做特殊处理,例如指令属性v-text='message'
    if (options.start) {
      options.start(tagName, attrs, unary, match.start, match.end);//tips:返回到parse函数中看start执行过程
    }
  }
  function parseEndTag (tagName, start, end) {
    var pos, lowerCasedTagName;
    if (start == null) { start = index; }
    if (end == null) { end = index; }
    if (tagName) {
      lowerCasedTagName = tagName.toLowerCase();
    }
    // Find the closest opened tag of the same type
    if (tagName) {
      for (pos = stack.length - 1; pos >= 0; pos--) {
        if (stack[pos].lowerCasedTag === lowerCasedTagName) {
          break
        }
      }
    } else {
      // If no tag name is provided, clean shop
      pos = 0;
    }
    if (pos >= 0) {
      // Close all the open elements, up the stack
      for (var i = stack.length - 1; i >= pos; i--) {
        if ("development" !== 'production' &&
            (i > pos || !tagName) &&
            options.warn) {
          options.warn(
            ("tag <" + (stack[i].tag) + "> has no matching end tag.")
          );
        }
        if (options.end) {
          options.end(stack[i].tag, start, end);
        }
      }
      // Remove the open elements from the stack
      stack.length = pos;
      lastTag = pos && stack[pos - 1].tag;
    } else if (lowerCasedTagName === 'br') {
      if (options.start) {
        options.start(tagName, [], true, start, end);
      }
    } else if (lowerCasedTagName === 'p') {
      if (options.start) {
        options.start(tagName, [], false, start, end);
      }
      if (options.end) {
        options.end(tagName, start, end);
      }
    }
  }
}

以上这个过程只是一个解析过程,将相应的属性放到相应的位置,但是还没有产生可执行代码,以下
generate函数的作用就是根据这些属性来产生相应的代码。

3 分析 generate(ast, options)

    // 温故下,返回结果ast是含有如下属性的对象
    // ```javascript
    // attrs:Array     //保存原始的html特性
    // attrsList:Array
    // attrsMap:Object
    // children:Array
    // parent:undefined
    // plain:false
    // static:false
    // staticRoot:false
    // tag:"div"
    // type:1
    // __proto__:Object
function generate (ast,options) {
  // save previous staticRenderFns so generate calls can be nested
  var prevStaticRenderFns = staticRenderFns;
  var currentStaticRenderFns = staticRenderFns = [];
  var prevOnceCount = onceCount;
  onceCount = 0;
  currentOptions = options;
  warn$3 = options.warn || baseWarn;
  transforms$1 = pluckModuleFunction(options.modules, 'transformCode');
  dataGenFns = pluckModuleFunction(options.modules, 'genData');
  platformDirectives$1 = options.directives || {};
  isPlatformReservedTag$1 = options.isReservedTag || no;
  var code = ast ? genElement(ast) : '_c("div")';  //主要函数,执行genElement(ast)
  staticRenderFns = prevStaticRenderFns;
  onceCount = prevOnceCount;
  return {
    render: ("with(this){return " + code + "}"),
    staticRenderFns: currentStaticRenderFns
  }
}

来看看 genElement(ast)

    //根据ast的属性是否有once,for,if,slot,component等属性执行不同函数,否则当普通元素处理并执行genData和genChildren函数,这两个函数都是在做字符串的拼装工作,最后返回拼装完成的code字符串
    function genElement (el) {
      if (el.staticRoot && !el.staticProcessed) {  //静态节点
        return genStatic(el)
      } else if (el.once && !el.onceProcessed) {    //v-once节点
        return genOnce(el)
      } else if (el.for && !el.forProcessed) {      //v-for节点
        return genFor(el)
      } else if (el.if && !el.ifProcessed) {        //v-if节点
        return genIf(el)
      } else if (el.tag === 'template' && !el.slotTarget) {
        return genChildren(el) || 'void 0'
      } else if (el.tag === 'slot') {               //处理slot
        return genSlot(el)
      } else {
        // component or element
        var code;
        if (el.component) {                          //处理组件节点
          code = genComponent(el.component, el);
        } else {
          var data = el.plain ? undefined : genData(el);   //处理元素节点的data
          var children = el.inlineTemplate ? null : genChildren(el, true);//处理元素节点的子元素
          code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
        }
        // module transforms
        for (var i = 0; i < transforms$1.length; i++) {
          code = transforms$1[i](el, code);
        }
        return code
      }
    }

来看看genData(el)

    //整个函数都是在做data字符串的拼装工作,最后返回data
    function genData (el) {
      var data = '{';
      // 首先处理指令,因为在el产生之前,指令可能会改变el的其他属性
      var dirs = genDirectives(el);
      if (dirs) { data += dirs + ','; }
      // 处理key属性
      if (el.key) {
        data += "key:" + (el.key) + ",";
      }
      // 处理ref属性
      if (el.ref) {
        data += "ref:" + (el.ref) + ",";
      }
      if (el.refInFor) {
        data += "refInFor:true,";
      }
      // 处理v-pre指令
      if (el.pre) {
        data += "pre:true,";
      }
      // record original tag name for components using "is" attribute
      if (el.component) {      //处理组件
        data += "tag:\"" + (el.tag) + "\",";
      }
      // 处理class和style属性
      for (var i = 0; i < dataGenFns.length; i++) {
        data += dataGenFns[i](el);
      }
      //处理特性 attributes
      if (el.attrs) {
        data += "attrs:{" + (genProps(el.attrs)) + "},";
      }
      //处理属性 DOM property
      if (el.props) {
        data += "domProps:{" + (genProps(el.props)) + "},";
      }
      //处理事件
      if (el.events) {
        data += (genHandlers(el.events)) + ",";
      }
      //处理本地事件
      if (el.nativeEvents) {
        data += (genHandlers(el.nativeEvents, true)) + ",";
      }
      处理slot目标
      if (el.slotTarget) {
        data += "slot:" + (el.slotTarget) + ",";
      }
      //处理scoped的slot
      if (el.scopedSlots) {
        data += (genScopedSlots(el.scopedSlots)) + ",";
      }
      //处理v-model
      if (el.model) {
        data += "model:{value:" + (el.model.value) + ",callback:" + (el.model.callback) + ",expression:" + (el.model.expression) + "},";
      }
      // 处理内联模板
      if (el.inlineTemplate) {
        var inlineTemplate = genInlineTemplate(el);
        if (inlineTemplate) {
          data += inlineTemplate + ",";
        }
      }
      data = data.replace(/,$/, '') + '}';
      // v-bind data wrap 处理v-bind
      if (el.wrapData) {
        data = el.wrapData(data);
      }
      return data
    }

来看看genDirectives(el)

    function genDirectives (el) {
      var dirs = el.directives;
      if (!dirs) { return }
      var res = 'directives:[';
      var hasRuntime = false;
      var i, l, dir, needRuntime;
      for (i = 0, l = dirs.length; i < l; i++) {
        dir = dirs[i];
        needRuntime = true;
        var gen = platformDirectives$1[dir.name] || baseDirectives[dir.name];
        if (gen) {
          // compile-time directive that manipulates AST.
          // returns true if it also needs a runtime counterpart.
          needRuntime = !!gen(el, dir, warn$3);
        }
        if (needRuntime) {
          hasRuntime = true;
          res += "{name:\"" + (dir.name) + "\",rawName:\"" + (dir.rawName) + "\"" + (dir.value ? (",value:(" + (dir.value) + "),expression:" + (JSON.stringify(dir.value))) : '') + (dir.arg ? (",arg:\"" + (dir.arg) + "\"") : '') + (dir.modifiers ? (",modifiers:" + (JSON.stringify(dir.modifiers))) : '') + "},";
        }
      }
      if (hasRuntime) {
        return res.slice(0, -1) + ']'
      }
    }

来看看genChildren

function genChildren (el, checkSkip) {
  var children = el.children;
  if (children.length) {
    var el$1 = children[0];
    // optimize single v-for
    if (children.length === 1 &&
        el$1.for &&
        el$1.tag !== 'template' &&
        el$1.tag !== 'slot') {
      return genElement(el$1)  //只有一个子元素并且有v-for属性时,递归调用genElement
    }
    var normalizationType = checkSkip ? getNormalizationType(children) : 0;
    return ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
  }
}

来看看genNode

function genNode (node) {
  if (node.type === 1) {  //type=1,表示是元素节点,递归调用genElement
    return genElement(node)
  } else {
    return genText(node)  //按照vue的用法,不是元素节点就只能是文本节点了
  }
}
/*
function genText (text) {
  return ("_v(" + (text.type === 2
    ? text.expression // no need for () because already wrapped in _s()
    : transformSpecialNewlines(JSON.stringify(text.text))) + ")")
}
 */

4 小结

  • compile过程即baseCompile过程:
    1. 调用parse函数对原始模板进行解析得到ast;
    2. 调用generate函数处理ast得到最终render函数
  • 本篇偏向对编译的整体过程分析,没有对诸如指令到底是怎么编译的进行分析,后面章节将结合实例具体分析指令等编译过程,让我们先瞅瞅vue一共提供了哪些内置指令:
    v-text
    v-html
    v-show
    v-if
    v-else
    v-else-if
    v-for
    v-on
    v-bind
    v-model
    v-pre
    v-cloak
    v-once

除此之外,还有三个特殊属性key,ref,slot以及内置组件component,transition,transition-group,keep-alive,slot,接下来的章节将对这些内容进行分析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容

  • vue2.0和1.0模板渲染的区别 Vue 2.0 中模板渲染与 Vue 1.0 完全不同,1.0 中采用的 Do...
    我是上帝可爱多阅读 1,281评论 0 4
  • 本文基于vue-2.4.4源码进行分析 模板编译是Vue 2.0中很重要的一个环节,它将template编译成re...
    fehysunny阅读 988评论 0 0
  • 深夜一点四十五分,我躺在床上,耳朵里塞着耳机,听着梁静茹的《会过去的》,突然想简单的用文字记录下这个时刻,记载着此...
    dandelio鲸阅读 85评论 0 0
  • 参加《做自己的CEO》的时间比较晚,导致前期的很多事情都没有做,当然也包括时间开销记录。不知道前期的热身活动有没有...
    汪隽子阅读 308评论 0 2