.9-Vue源码之AST(5)
上節跑完了超長的parse函數:
// Line-9261function baseCompile(template, options) {// Done!var ast = parse(template.trim(), options);// go! optimize(ast, options);var code = generate(ast, options);return {ast: ast,render: code.render,staticRenderFns: code.staticRenderFns}}返回一個ast對象,包括attrs、attrsList、attrsMap、children、plain、tag、type等屬性,如圖:。
包含了DOM字符串中節點類型、屬性、文本內容,接下來進入優化函數:optimize。
// Line-8648function optimize(root, options) {if (!root) {return}// 緩存靜態標簽isStaticKey = genStaticKeysCached(options.staticKeys || '');isPlatformReservedTag = options.isReservedTag || no;// 標記非靜態節點markStatic$1(root);// 標記靜態節點markStaticRoots(root, false);}首先函數會對靜態屬性進行緩存:
// Line-8558function genStaticKeys$1(keys) {return makeMap('type,tag,attrsList,attrsMap,plain,parent,children,attrs' +(keys ? ',' + keys : ''))}可以看到,上面的好像全是靜態的屬性。
接下來調用markStatic$1標記非靜態節點:
// Line-8648function markStatic$1(node) {// 判斷是否是靜態節點node.static = isStatic(node);if (node.type === 1) {// 靜態節點不包括slot和template標簽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);// 子節點為false 父節點也是falseif (!child.static) {node.static = false;}}}}函數對節點做了三重判斷,首先用isStatic方法判斷類型。
// Line-8621function isStatic(node) {if (node.type === 2) { // expressionreturn false}if (node.type === 3) { // textreturn true}return !!(node.pre || (!node.hasBindings && // no dynamic bindings!node.if && !node.for && // not v-if or v-for or v-else!isBuiltInTag(node.tag) && // not a built-inisPlatformReservedTag(node.tag) && // not a component!isDirectChildOfTemplateFor(node) && // 判斷是否v-for的子節點Object.keys(node).every(isStaticKey) // 遍歷判斷屬性是否靜態 ))}排除type為2的表達式和3的文本,然后對屬性進行遍歷,若存在v-的動態屬性,則會出現對應的屬性,注釋已經寫出來了,這里就不做說明了。
由于本案例只有一個動態文本,所以這里返回的是true。
?
接著判斷標簽是否是slot或者template,此類動態標簽不屬性靜態節點。
最后對子節點,即children屬性中的內容進行遞歸處理。
函數完成后,ast對象會多一個屬性,即static:false。
?
剩下一個是對靜態節點進行標記:
// Line-8587function 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) {walkThroughConditionsBlocks(node.ifConditions, isInFor);}}}由于本例子節點是純文本,所以staticRoot屬性被標記為false。
?
經過optimize函數,ast對象被添加了兩個靜態屬性:
?
最后是generate函數:
// Line-8799function generate(ast, options) {// 保存上一個屬性值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;// 將ast對象轉換為字符串var code = ast ? genElement(ast) : '_c("div")';staticRenderFns = prevStaticRenderFns;onceCount = prevOnceCount;return {render: ("with(this){return " + code + "}"),staticRenderFns: currentStaticRenderFns}}這個函數主要部分是code那一塊,將ast對象轉換成自定義的字符串形式,但是由于本例只有很簡單的屬性和文本,所以摘取分支看一下。
// Line-8823function genElement(el) {if ( /* staticRoot,once,for,if,template,slot */ ) {/* code */} else {// component or elementvar code;if (el.component) {code = genComponent(el.component, el);} else {// 1var data = el.plain ? undefined : genData(el);// 2var children = el.inlineTemplate ? null : genChildren(el, true);code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";}// module transformsfor (var i = 0; i < transforms$1.length; i++) {code = transforms$1[i](el, code);}return code}}跳過所有屬性的判斷,直接進入最后的分支,在這里對節點與子節點分別做了處理。
首先看genData函數。
可以看到跳過了大多數的判斷,直接進入attrs,調用genProps函數并將之前{name:id,value:app}的對象傳了進去。
// Line-9146function genProps(props) {var res = '';// 將name,value拼接成 "name":"value", 形式的字符串for (var i = 0; i < props.length; i++) {var prop = props[i];res += "\"" + (prop.name) + "\":" + (transformSpecialNewlines(prop.value)) + ",";}// 去掉最后的逗號return res.slice(0, -1)}遍歷attrs數組,將鍵值拼接成對應的字符串,本例只有一個id屬性,拼接后返回。
處理完后調用正則將最后的逗號去掉并加上對應的大括號,最后的wrapData屬性也沒有,所以直接返回data,最終結果如圖所示:
?
第一步完事后,進行子節點處理:
// Line-8823function genElement(el) {if ( /* code... */ ) {/* code... */} else {var code;/* code... */// 返回節點信息var data = el.plain ? undefined : genData(el);// 子節點var children = el.inlineTemplate ? null : genChildren(el, true);code = "_c('" + (el.tag) + "'" + (data ? ("," + data) : '') + (children ? ("," + children) : '') + ")";// module transformsfor (var i = 0; i < transforms$1.length; i++) {code = transforms$1[i](el, code);}return code}} // Line-8823function genChildren(el, checkSkip) {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 genElement(el$1)}// 對存在子DOM節點的對象做處理 不存在返回0var normalizationType = checkSkip ? getNormalizationType(children) : 0;return ("[" + (children.map(genNode).join(',')) + "]" + (normalizationType ? ("," + normalizationType) : ''))}}// Line-9107function genNode(node) {if (node.type === 1) {return genElement(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))) + ")")}調用genChildren后同樣返回一個包裝后的字符串:。
?
最后,將節點與內容結合,生成一個總的字符串,如圖所示:
返回到generate函數:
// Line-8799function generate(ast, options) {/* code */var code = ast ? genElement(ast) : '_c("div")';staticRenderFns = prevStaticRenderFns;onceCount = prevOnceCount;return {render: ("with(this){return " + code + "}"),staticRenderFns: currentStaticRenderFns}}輸出一個對象,返回到最初的baseCompile函數,除了ast,多出來的對象內容如圖:
這個對象返回到compileToFunctions函數,目前進度是這樣的:
// Line-9326function compileToFunctions(template, options, vm) {options = options || {};/* new Function檢測 *//* 緩存查詢 */// compilevar compiled = compile(template, options);/* 輸出返回的error和tips */// 將字符串代碼轉化為函數var res = {};var fnGenErrors = [];res.render = makeFunction(compiled.render, fnGenErrors);var l = compiled.staticRenderFns.length;res.staticRenderFns = new Array(l);for (var i = 0; i < l; i++) {res.staticRenderFns[i] = makeFunction(compiled.staticRenderFns[i], fnGenErrors);}/* checkError */return (functionCompileCache[key] = res)}返回的compiled如圖所示:
接下來將render字符串重新轉換為函數,makeFunction方法很簡單,就是使用new Function生成一個函數:
// Line-9275function makeFunction(code, errors) {try {return new Function(code)} catch (err) {errors.push({err: err,code: code});return noop}}結果如圖:
由于res.staticRenderFns是空,所以最后直接把該res緩存進functionCompileCache然后返回。
?
這個函數完事后,返回到了$mount方法中,很久之前的一個函數,內容如下:
// Line-9553Vue$3.prototype.$mount = function(el,hydrating) {el = el && query(el);/* warning */var options = this.$options;// resolve template/el and convert to render functionif (!options.render) {var template = options.template;if (template) {/* 獲取template */} else if (el) {template = getOuterHTML(el);}if (template) {/* compile start */if ("development" !== 'production' && config.performance && mark) {mark('compile');}var ref = compileToFunctions(template, {shouldDecodeNewlines: shouldDecodeNewlines,delimiters: options.delimiters}, this);var render = ref.render;var staticRenderFns = ref.staticRenderFns;options.render = render;options.staticRenderFns = staticRenderFns;/* compile end */}}return mount.call(this, el, hydrating)};調用完compileToFunctions后,返回的對象包含一個render函數,一個staticRenderFns屬性,分別掛載到options參數上,然后再次調用mount方法。
結束~
轉載于:https://www.cnblogs.com/QH-Jimmy/p/6991987.html
總結
以上是生活随笔為你收集整理的.9-Vue源码之AST(5)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2016 湖南省省赛B题《有向无环图》
- 下一篇: 小程序上拉刷新