AST是指抽象語法樹(abstract syntax tree),或者語法樹(syntax tree),是源代碼的抽象語法結構的樹狀表現形式。Vue在mount過程當中,template會被編譯成AST語法樹。
而後,通過generate(將AST語法樹轉化成render function字符串的過程)獲得render函數,返回VNode。VNode是Vue的虛擬DOM節點,裏面包含標籤名、子節點、文本等信息,關於VNode的學習來自:https://blog.csdn.net/qq_3626...
以javascript
<div id="test"> 請輸入:<input type="text" v-model="message"><br/> </div>
var stack = []; var preserveWhitespace = options.preserveWhitespace !== false; var root; var currentParent; var inVPre = false; var inPre = false; var warned = false; function warnOnce (msg){ } function closeElement (element){ } //調用parseHTML,這裏對options的內容省略 parseHTML(template,options);
定義一些變量,root用於存放AST樹根節點,currentParent存放當前父元素,stack用來輔助樹創建的棧。接着調用parseHTML函數進行轉化,傳入template和options。
options的結構以下:html
parseHTML內容大綱java
last = html; //確認html不是相似<script>,<style>這樣的純文本標籤 if (!lastTag || !isPlainTextElement(lastTag)) { var textEnd = html.indexOf('<');//判斷html字符串是否以<開頭 if (textEnd === 0) { // 這裏的Comment是Vue定義的正則表達式,判斷html是否是<!-- -->註釋 //var comment = /^<!\--/; if (comment.test(html)) { var commentEnd = html.indexOf('-->'); if (commentEnd >= 0) { if (options.shouldKeepComment) { options.comment(html.substring(4, commentEnd)); } advance(commentEnd + 3); continue } } //判斷是否處理向下兼容的註釋,相似<![if !IE]> //var conditionalComment = /^<!\[/; if (conditionalComment.test(html)) { var conditionalEnd = html.indexOf(']>'); if (conditionalEnd >= 0) { advance(conditionalEnd + 2); continue } } //獲取<!DOCTYPE開頭的標籤內容 // var doctype = /^<!DOCTYPE [^>]+>/i; var doctypeMatch = html.match(doctype); if (doctypeMatch) { advance(doctypeMatch[0].length); continue } //判斷此段html是否結束標籤 // var endTag = new RegExp(("^<\\/" + qnameCapture + "[^>]*>")); // var qnameCapture = "((?:" + ncname + "\\:)?" + ncname + ")"; // var ncname = '[a-zA-Z_][\\w\\-\\.]*'; var endTagMatch = html.match(endTag); if (endTagMatch) { var curIndex = index; advance(endTagMatch[0].length); parseEndTag(endTagMatch[1], curIndex, index); continue } // 匹配開始標籤,獲取match對象 var startTagMatch = parseStartTag(); if (startTagMatch) { handleStartTag(startTagMatch); if (shouldIgnoreFirstNewline(lastTag, html)) { advance(1); } continue } var text = (void 0), rest = (void 0), next = (void 0); if (textEnd >= 0) { rest = html.slice(textEnd); while ( !endTag.test(rest) && !startTagOpen.test(rest) && !comment.test(rest) && !conditionalComment.test(rest) ) { // 處理文本中的<字符 next = rest.indexOf('<', 1); if (next < 0) { break } textEnd += next; rest = html.slice(textEnd); } text = html.substring(0, textEnd); advance(textEnd); } if (textEnd < 0) { text = html; html = ''; } if (options.chars && text) { options.chars(text); } } else { //代碼省略 } if (html === last) { //代碼省略 } }
parseHTML使用while循環對傳進來的html進行解析。首先獲取<標籤索引var textEnd = html.indexOf('<');
若是textEnd爲0 說明是標籤<xx>或者</xx>,再用正則匹配是否爲<!---->註釋標籤,若是不是,再判斷是否爲向下兼容放入註釋,如<![if !IE]>(詳情見),若是不是,再判斷是否已<!DOCTYPE開頭的文檔頭部標籤,若是不是,再判斷當前是否結束標籤</xx>。var endTagMatch = html.match(endTag);
匹配不到那麼就是開始標籤,調用parseStartTag()函數解析。正則表達式
function parseStartTag () { var start = html.match(startTagOpen); if (start) { var match = { tagName: start[1],//標籤名,本文的例子div attrs: [], start: index//0 };//定義match對象 advance(start[0].length);//index=4,html=" id="test">... var end, attr; //match對象的attrs //index=14 while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { advance(attr[0].length); match.attrs.push(attr); } // 在第二次while循環後 end匹配到結束標籤 => ['>',''] if (end) { match.unarySlash = end[1]; advance(end[0].length); match.end = index; return match } } }
parseStartTag()構建一個match對象,對象裏面包含標籤名(tagName),標籤屬性(attrs),<左開始標籤的位置(start),>右開始標籤的位置(end)。本文的例子,程序第一次進入該函數,因此tagName:div,start:0,end:14 match數組
function advance (n) { index += n; html = html.substring(n); }
advance函數將局部變量index日後推 並切割字符串。app
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) || !!unarySlash; var l = match.attrs.length; var attrs = new Array(l); //解析html屬性值{name:'id',value:'test'}的格式 for (var i = 0; i < l; i++) { var args = match.attrs[i]; 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] || ''; var shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' ? options.shouldDecodeNewlinesForHref : options.shouldDecodeNewlines; attrs[i] = { name: args[1], // 處理轉義字符 value: decodeAttr(value, shouldDecodeNewlines) }; } // 將切割出來的字符串轉換爲AST if (!unary) { stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs }); //設置結束標籤 lastTag = tagName; } if (options.start) { options.start(tagName, attrs, unary, match.start, match.end); } }
在該函數中,對match進行了二次處理,根據標籤名、屬性生成一個新對象,push到最開始的stack數組中。
因爲匹配的是起始標籤,因此也會以這個標籤名結束,此處的lastTag就是div。
函數最後調用了parse內部聲明的方法startide
function start (tag, attrs, unary) { //檢查命名空間是不是svg或者math var ns = (currentParent && currentParent.ns) || platformGetTagNamespace(tag); // handle IE svg bug /* istanbul ignore if */ if (isIE && ns === 'svg') { attrs = guardIESVGBug(attrs); } //建立element元素,element其實就是{type: 1, //tag: "div", //attrsList: [{name: "id", value: "test"}]], //attrsMap: makeAttrsMap(attrs), //parent:undefined //children: []}的一個對象 var element = createASTElement(tag, attrs, currentParent); if (ns) { element.ns = ns; } //排除script,style標籤 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.' ); } // apply pre-transforms for (var i = 0; i < preTransforms.length; i++) { //若html裏面有v-model等指令,經過preTransforms進行轉換 element = preTransforms[i](element, options) || element; } if (!inVPre) { // 判斷是否有v-pre屬性 processPre(element); if (element.pre) { inVPre = true; } } //判斷標籤名是否是pre if (platformIsPreTag(element.tag)) { inPre = true; } if (inVPre) { processRawAttrs(element); } else if (!element.processed) { // 處理v-for processFor(element); // 處理v-if processIf(element); // 處理v-once processOnce(element); // element-scope stuff processElement(element, options); } // 樹結構的root節點處理 if (!root) { root = element; checkRootConstraints(root); } else if (!stack.length) { // allow root elements with 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 { closeElement(element); } }
對標籤名進行校驗,同時對屬性進行更細緻的處理,如v-pre,v-for,v-if等。最後調用processElement(element, options)對當前的樹節點元素進行處理,具體以下:svg
processKey(element); // 檢測是不是空屬性節點 element.plain = !element.key && !element.attrsList.length; // 處理:ref或v-bind:ref屬性 processRef(element); //處理標籤名爲slot的狀況 processSlot(element); // 處理is或v-bind:is屬性 processComponent(element); for (var i = 0; i < transforms.length; i++) { element = transforms[i](element, options) || element; } //處理屬性 processAttrs(element);
start()生成element對象,再鏈接元素的parent和children節點,最後push到棧中,此時棧中第一個元素生成。結構以下:函數
接下來開始第二次循環,html變成了 請輸入:<input type="text" v-model="message">
</div>,所以此次解析的是文字:'請輸入',具體代碼分析在下一次~~~學習