Vue源码解析之AST语法树(三)

parse解析完之后,将生成的ast返回到baseCompile,接下来就是调用optimize方法对ast进行优化。

var createCompiler = createCompilerCreator(function baseCompile (
  template,
  options
) {
  //获取ast
  var ast = parse(template.trim(), options);
  if (options.optimize !== false) {
    optimize(ast, options);
  }
  var code = generate(ast, options);
  return {
    ast: ast,
    render: code.render,
    staticRenderFns: code.staticRenderFns
  }
});

optimize

function optimize (root, options) {
  if (!root) { return }
  //对静态标签进行缓存
  isStaticKey = genStaticKeysCached(options.staticKeys || '');
  isPlatformReservedTag = options.isReservedTag || no;
  //标记所有非静态节点
  markStatic$1(root);
  //标记所有静态root节点
  markStaticRoots(root, false);
}

optimize方法通过genStaticKeysCached缓存了所有静态标签,调用markStatic$1(root)标记所有非静态节点,调用markStaticRoots(root, false)标记静态root节点。

function markStatic$1 (node) {
   //判断是否是静态节点,isStatic代码在下面
  node.static = isStatic(node);
  if (node.type === 1) {
    //过滤掉slot标签和template标签
    //原因:组件不能变成slot节点
    //静态的slot节点内容不能热加载
    if (
      !isPlatformReservedTag(node.tag) &&
      node.tag !== 'slot' &&
      node.attrsMap['inline-template'] == null
    ) {
      return
    }
    //循环递归标记节点
    for (var i = 0, l = node.children.length; i < l; i++) {
      var child = node.children[i];
      markStatic$1(child);
      if (!child.static) {
        node.static = false;
      }
    }
    if (node.ifConditions) {
      /**/
    }
  }
}
function isStatic (node) {
  if (node.type === 2) { // 判断是不是类似{{message}}这样的表达式
    return false
  }
  if (node.type === 3) { // 判断是不是纯文本
    return true
  }
  return !!(node.pre || (
    !node.hasBindings && // 是否动态绑定
    !node.if && !node.for && //是否v-if or v-for or v-else
    !isBuiltInTag(node.tag) && // not a built-in
    isPlatformReservedTag(node.tag) && // not a component
    !isDirectChildOfTemplateFor(node) &&
    Object.keys(node).every(isStaticKey) //遍历判断属性是否静态
  ))
}

markStaticRoots

function markStaticRoots (node, isInFor) {
  if (node.type === 1) {
    if (node.static || node.once) {
      node.staticInFor = isInFor;
    }
    // 作为静态节点 必须有子节点并且不为纯文本 否则更新消耗较大
    if (node.static && node.children.length && !(
      node.children.length === 1 &&
      node.children[0].type === 3
    )) {
      node.staticRoot = true;
      return
    } else {
      node.staticRoot = false;
    }
    //进行递归标记
    if (node.children) {
      for (var i = 0, l = node.children.length; i < l; i++) {
        markStaticRoots(node.children[i], isInFor || !!node.for);
      }
    }
    if (node.ifConditions) {
      /**/
    }
  }
}

经过optimize函数,ast对象增加加了两个属性,如图:

Vue源码解析之AST语法树(三)
接下来调用generate方法,将ast对象转换成Vue自定义的字符串形式。

generate

function generate (
  ast,
  options
) {
  //根据options创建CodegenState对象
  var state = new CodegenState(options);
  //调用genElement将ast对象转换为字符串
  var code = ast ? genElement(ast, state) : '_c("div")';
  return {
    render: ("with(this){return " + code + "}"),
    staticRenderFns: state.staticRenderFns
  }
}

genElement

function genElement (el, state) {
  if (el.staticRoot && !el.staticProcessed) {
    return genStatic(el, state)
  } else if (el.once && !el.onceProcessed) {
    return genOnce(el, state)
  } else if (el.for && !el.forProcessed) {
    return genFor(el, state)
  } else if (el.if && !el.ifProcessed) {
    return genIf(el, state)
  } else if (el.tag === 'template' && !el.slotTarget) {
    return genChildren(el, state) || 'void 0'
  } else if (el.tag === 'slot') {
    return genSlot(el, state)
  } else {
    // component or element
    var code;
    if (el.component) {
      code = genComponent(el.component, el, state);
    } else {
      //本例子进入这里,调用genData$2
      var data = el.plain ? undefined : genData$2(el, state);

      var children = el.inlineTemplate ? null : genChildren(el, state, true);
      code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";
    }
    // module transforms
    for (var i = 0; i < state.transforms.length; i++) {
      code = state.transforms[i](el, code);
    }
    return code
  }
}

genData$2

function genData$2 (el, state) {
  var data = '{';

  // 首先对directives进行处理
  // directives可能会对el上的其他属性有影响,所以先处理
  var dirs = genDirectives(el, state);
  if (dirs) { data += dirs + ','; }

 //根据本文的例子,没有执行的判断,代码都省略了
 /*处理key,ref,refInFor,pre,component*/

  // module data generation functions
  for (var i = 0; i < state.dataGenFns.length; i++) {
    data += state.dataGenFns[i](el);//调用genData对class进行处理
  }
  if (el.attrs) {
    //进入该判断,调用genProps,对el的属性进行处理
    data += "attrs:{" + (genProps(el.attrs)) + "},";
  }
  /*处理props,events,nativeEvents,slotTarget,scopedSlots,model,inlineTemplate*/
  data = data.replace(/,$/, '') + '}';
  // v-bind data wrap
  if (el.wrapData) {
    data = el.wrapData(data);
  }
  // v-on data wrap
  if (el.wrapListeners) {
    data = el.wrapListeners(data);
  }
  return data
}

genData$2跳过了大多数的判断,直接进入attrs,调用genProps函数

function genProps (props) {
  var res = '';
    // 将属性名,属性值拼接成 "属性名":"属性值"形式的字符串
    //本文例子"id":"test"
  for (var i = 0; i < props.length; i++) {
    var prop = props[i];
    /* istanbul ignore if */
    {
      res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ",";
    }
  }
  return res.slice(0, -1)
}

genData$2最终返回的data:

Vue源码解析之AST语法树(三)

接下来开始对子节点进行处理

genChildren

function genChildren (
  el,
  state,
  checkSkip,
  altGenElement,
  altGenNode
) {
  var children = el.children;
  if (children.length) {
    var el$1 = children[0];
    //对v-for进行简单优化
    if (children.length === 1 &&
      el$1.for &&
      el$1.tag !== 'template' &&
      el$1.tag !== 'slot'
    ) {
      return (altGenElement || genElement)(el$1, state)
    }
    var normalizationType = checkSkip
      ? getNormalizationType(children, state.maybeComponent)
      : 0;
    var gen = altGenNode || genNode;
    return ("[" + (children.map(function (c) { return gen(c, state); }).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))
  }
}

function genNode (node, state) {
  if (node.type === 1) {
    return genElement(node, state)
  } if (node.type === 3 && node.isComment) {
    return genComment(node)
  } else {
    return genText(node)
  }
}

function genText (text) {
  return ("_v(" + (text.type === 2
    ? text.expression // no need for () because already wrapped in _s()
    : transformSpecialNewlines(JSON.stringify(text.text))) + ")")
}

function genComment (comment) {
  return ("_e(" + (JSON.stringify(comment.text)) + ")")
}

最终转换后的字符串的结果为:

Vue源码解析之AST语法树(三)
正好对应Vue对v-model的解析过程:https://segmentfault.com/a/11...

code:

Vue源码解析之AST语法树(三)

compileToFunction

接下来compileToFunction将生成的code字符串代码转化为函数

// turn code into functions
    var res = {};
    var fnGenErrors = [];
    //createFunction就返回了new Function(code)
    //这里的render就是上文的code字符串
    res.render = createFunction(compiled.render, fnGenErrors);
    res.staticRenderFns = compiled.staticRenderFns.map(function (code) {
      return createFunction(code, fnGenErrors)
    });

compileToFunction返回的对象信息:

Vue源码解析之AST语法树(三)

$amount

var ref = compileToFunctions(template, {
        shouldDecodeNewlines: shouldDecodeNewlines,
        shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this);
      var render = ref.render;
      var staticRenderFns = ref.staticRenderFns;
      options.render = render;
      options.staticRenderFns = staticRenderFns;

$amount调用compileToFunctions后,将返回的对象包含的render函数和staticRenderFns属性,挂载到options参数上,然后再次调用mount。
整个函数调用过程:
Vue源码解析之AST语法树(三)

相关推荐