上一节讲完了超长的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;
//
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对象:,包含了标签类型、属性、文本内容等。
先结束了吧。