上一节讲完了超长的start函数,也同时完结了handleStartTag函数,接着continue进入下一轮while循环。
此时剩余的字符串状态如图:,切掉了<div id='app'>。
再次进入while循环时,发生了一些变化:
// Line-7672 function parseHTML(html, options) { /* code */ while (html) { last = html; if (!lastTag || !isPlainTextElement(lastTag)) { var textEnd = html.indexOf('<'); // 此时字符串不是以<开头 所以不会进入此条件 if (textEnd === 0) { // ... } var text = (void 0), rest$1 = (void 0), next = (void 0); if (textEnd >= 0) { // 截取<字符索引 => </div> rest$1 = html.slice(textEnd); // 处理文本中的<字符 while (!endTag.test(rest$1) && !startTagOpen.test(rest$1) && !comment.test(rest$1) && !conditionalComment.test(rest$1) ) { next = rest$1.indexOf('<', 1); if (next < 0) { break } textEnd += next; rest$1 = html.slice(textEnd); } // 获取中间的字符串 => {{message}} text = html.substring(0, textEnd); advance(textEnd); } // 当字符串没有<时 if (textEnd < 0) { text = html; html = ''; } // 另外一个函数 if (options.chars && text) { options.chars(text); } } else { /* code */ } if (html === last) { /* code */ } } // Clean up any remaining tags parseEndTag(); // fn... }
第一次进入while循环时,由于字符串以<开头,所以进入startTag条件,并进行AST转换,最后将对象弹入stack数组中。
而这一次,字符串开头为{,所以会继续执行下面的代码。代码将{{message}}作为text抽离出来,并调用了参数中另外一个函数:options.chars。
// Line-8167 function chars(text) { if (!currentParent) { // 提示必须有一个DOM根节点 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 if (isIE && currentParent.tag === 'textarea' && currentParent.attrsMap.placeholder === text) { return } var children = currentParent.children; // text => {{message}} text = inPre || text.trim() ? isTextTag(currentParent) ? text : decodeHTMLCached(text) : preserveWhitespace && children.length ? ' ' : ''; if (text) { var expression; if (!inVPre && text !== ' ' && (expression = parseText(text, delimiters))) { // 将解析后的text弄进children数组 children.push({ type: 2, expression: expression, text: text }); } else if (text !== ' ' || !children.length || children[children.length - 1].text !== ' ') { children.push({ type: 3, text: text }); } } }
本函数的核心为parseText对text的处理,即{{message}}。
// Line-7928 function parseText(text, delimiters) { // 正则选择 var tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE; // 在这里调用test方法后lasatIndex会变化 if (!tagRE.test(text)) { return } var tokens = []; var lastIndex = tagRE.lastIndex = 0; var match, index; // 匹配到中间的文本 while ((match = tagRE.exec(text))) { index = match.index; // 将{{message}}之前的文本push进去 if (index > lastIndex) { tokens.push(JSON.stringify(text.slice(lastIndex, index))); } // 该方法对特殊字符进行处理 本例暂时用不上 // 返回的仍然是message字符串 var exp = parseFilters(match[1].trim()); // _s(message) tokens.push(("_s(" + exp + ")")); lastIndex = index + match[0].length; } if (lastIndex < text.length) { // push}}后面的文本 tokens.push(JSON.stringify(text.slice(lastIndex))); } return tokens.join('+') }
实际上text可分为3个部分,{{之前的,{{}}中间包裹的,}}之后的,函数分别将三者抽离出来,push进了tokens,最后用+连接并返回一个字符串:
返回后,将此字符串作为值,和其余属性一个添加到children数组中:
处理完后,进入下一轮while循环。
剩余的字符串为</div>,所以进入第一个循环,并且匹配到EndTag的分支。
// Line-7672 function parseHTML(html, options) { /* code */ while (html) { /* code */ var textEnd = html.indexOf('<'); if (textEnd === 0) { /* code */ var endTagMatch = html.match(endTag); if (endTagMatch) { var curIndex = index; advance(endTagMatch[0].length); parseEndTag(endTagMatch[1], curIndex, index); continue } /* code */ } /* code */ } /* code */ }
进入endTag分支后,匹配到的endTagMatch如图所示:
将当前索引保存为curIndex,然后根据匹配到的字符串往前推index,调用parseEndTag函数进行处理。
// Line-7863 function parseEndTag(tagName, start, end) { // 参数修正 var pos, lowerCasedTagName; if (start == null) { start = index; } if (end == null) { end = index; } if (tagName) { lowerCasedTagName = tagName.toLowerCase(); } // 获取最近的匹配标签 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) { 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; // 0 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); } } } // Line-8154 function end() { // 获取对象与文本 var element = stack[stack.length - 1]; var lastNode = element.children[element.children.length - 1]; // type是2 跳过 if (lastNode && lastNode.type === 3 && lastNode.text === ' ' && !inPre) { element.children.pop(); } // pop stack stack.length -= 1; // 变成undefined了 currentParent = stack[stack.length - 1]; endPre(element); } // Line-8010 function endPre(element) { if (element.pre) { inVPre = false; } // tag === pre? if (platformIsPreTag(element.tag)) { inPre = false; } }
这个函数对闭合标签进行配对,并对应将stack数组进行变动,由于本例只有一个div,所以stack被清空。
完事后,continue进入下一轮循环,由于字符串全部被切割完,此时html为空字符串,此时while循环结束,进入下一个代码段:
// Line-7672 function parseHTML(html, options) { /* code */ while (html) { /* code */ } // Clean up any remaining tags parseEndTag(); /* 一些方法 */ }
字符串解析完后,再次调用parseEndTag进行收尾工作,函数内部将pos置0,stack置空。
回到了parse函数,并返回了root,即解析后的AST对象:,包含了标签类型、属性、文本内容等。
先结束了吧。