Vue源碼解析之AST語法樹(三)

parse解析完以後,將生成的ast返回到baseCompile,接下來就是調用optimize方法對ast進行優化。javascript

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節點。java

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對象增長加了兩個屬性,如圖:node

clipboard.png
接下來調用generate方法,將ast對象轉換成Vue自定義的字符串形式。express

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函數segmentfault

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:緩存

clipboard.png

接下來開始對子節點進行處理app

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)) + ")")
}

最終轉換後的字符串的結果爲:函數

clipboard.png
正好對應Vue對v-model的解析過程:https://segmentfault.com/a/11...優化

code:ui

clipboard.png

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返回的對象信息:

clipboard.png

$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。
整個函數調用過程:
clipboard.png

相關文章
相關標籤/搜索