×

vue错误处理:parseHTML函数解析器遇到结束标签

作者:abc1232022.07.14来源:Web前端之家浏览:3740评论:0
关键词:vuejs

vue错误处理:parseHTML函数解析器遇到结束标签。

接下来我们将会讲解当 textEnd === 0 解析器遇到结束标签,parse 结束标签的代码如下:

// End tag:
var endTagMatch = html.match(endTag);
if (endTagMatch) {
    var curIndex = index;
    advance(endTagMatch[0].length);
    parseEndTag(endTagMatch[1], curIndex, index);
    continue
}

match函数匹配正则endTag

首先调用 html 字符串的 match 函数匹配正则 endTag ,将结果保存在常量endTagMatch中。正则 endTag 用来匹配结束标签,并且拥有一个捕获组用来捕获标签名字,比如有如下html 字符串:

<div></div>

endTagMatch 输出如下:

endTagMatch = [
  '</div>',
  'div'
]

第一个元素是整个匹配到的结束标签字符串

第二个元素是对应的标签名字。

如果匹配成功 if 语句块的代码将被执行,首先使用 curIndex 常量存储当前 index 的值,然后调用 advance 函数,并以 endTagMatch[0].length 作为参数,接着调用了 parseEndTag 函数对结束标签进行解析,传递给 parseEndTag 函数的三个参数分别是:标签名以及结束标签在 html 字符串中起始和结束的位置,最后调用 continue 语句结束此次循环。

关键 parseEndTag 函数代码

现在我们来讲解下关键 parseEndTag 函数代码如下:

function parseEndTag(tagName, start, end) {
	var pos, lowerCasedTagName;
	if (start == null) {
		start = index;
	}
	if (end == null) {
		end = index;
	}
	// Find the closest opened tag of the same type
	if (tagName) {
		lowerCasedTagName = tagName.toLowerCase();
		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) {
		// Close all the open elements, up the stack
		for (var i = stack.length - 1; i >= pos; i--) {
			if (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);
		}
	}
}

你需要知道 parseEndTag 函数调用之前已经获得到了结束标签的名字以及结束标签在html(template)字符串中的起始和结束位置。 但是这并不代表着 html parser 结束了。

为什么?

还记得我们之前讲的 stack 栈吗? 之前我们讲到通过stack可以检测是否有非一元标签是否微写闭合标签,接下来还会处理 stack 栈中剩余的标签。

除了这些功能之外,parseEndTag函数还会做一件事儿,如果你感兴趣你可以在任何html文件中写下如下内容:

<body>
  </br>
  </p>
</body>

上面的html片段中,我们分别写了</br>、</p>的结束标签,但注意我们并没有写起始标签,然后浏览器是能够正常解析他们的,其中 </br> 标签被正常解析为 <br> 标签,而</p>标签被正常解析为 <p></p> 。除了 br 与 p 其他任何标签如果你只写了结束标签那么浏览器都将会忽略。所以为了与浏览器的行为相同,parseEndTag 函数也需要专门处理br与p的结束标签,即:</br> 和</p>。

总结parseEndTag 函数作用

  • 检测是否缺少闭合标签

  • 处理 stack 栈中剩余的标签

  • 解析</br> 与标签,与浏览器的行为相同

当一个函数拥有两个及以上功能的时候,最常用的技巧就是通过参数进行控制,还记得jQuery中的Access 吗?parseEndTag 函数接收三个参数,这三个参数其实都是可选的,根据传参的不同其功能也不同。

  • 第一种是处理普通的结束标签,此时三个参数都传递

  • 第二种是只传递第一个参数

  • 第三种是不传递参数,处理 stack 栈剩余未处理的标签。

代码并不复杂我们一起来看下吧!

var pos, lowerCasedTagName;
if (start == null) {
    start = index;
}
if (end == null) {
    end = index;
}

定了两个变量:pos和 lowerCasedTagName,其中变量 pos 会在后面用于判断 html 字符串是否缺少结束标签,lowerCasedTagName 变量用来存储 tagName 的小写版。

接着是两句if 语句,当 start 和 end 不存在时,将这两个变量的值设置为当前字符流的读入位置,即index。

所以当我们看到这两个 if 语句时,我们就应该能够想到:parseEndTag 函数的第二个参数和第三个参数都是可选的。

其实这种使用 parseEndTag 函数的方式我们在handleStartTag 函数中见过,当时我们没有对其进行讲解一起来回顾下。

if (expectHTML) {
  if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
    parseEndTag(lastTag)
  }
  if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
    parseEndTag(tagName)
  }
}

我们知道 lastTag 引用的是stack栈顶的元素,也就是最近(或者说上一次)遇到的开始标签,所以如下判断条件:

lastTag === 'p' && isNonPhrasingTag(tagName)

这里想表达的意思是:最近一次遇到的开始标签是 p 标签,并且当前正在解析的开始标签必须不能是段落式内容(Phrasing content)模型,这时候 if 语句块的代码才会执行,即调用parseEndTag(lastTag)。

首先大家要知道每一个 html 元素都拥有一个或多个内容模型(content model),其中p 标签本身的内容模型是流式内容(Flow content),并且 p 标签的特性是只允许包含段落式内容(Phrasing content)。

所以条件成立的情况如下:

<p><h1></h1></p>

在解析上面这段 html 字符串的时候,首先遇到p标签的开始标签,此时lastTag被设置为 p ,紧接着会遇到 h1 标签的开始标签,由于 h2 标签的内容模型属于非段落式内容(Phrasing content)模型,所以会立即调用 parseEndTag(lastTag) 函数闭合 p 标签,此时由于强行插入了</p> 标签,所以解析后的字符串将变为如下内容:

<p></p><h2></h2></p>

接着,继续解析该字符串,会遇到 <h2></h2> 标签并正常解析之,最后解析器会遇到一个单独的p 标签的结束标签,即:</p>。

这个时候就回到了我们前面讲过的,当解析器遇到 p 标签或者 br 标签的结束标签时会补全他们,最终<p><h2></h2></p> 这段 html 字符串将被解析为:

<p></p><h2></h2><p></p>

而这也就是浏览器的行为,以上是第一个if 分支的意义。还有第二个if分支,它的条件如下:

canBeLeftOpenTag(tagName) && lastTag === tagName

以上条件成立的意思是:当前正在解析的标签是一个可以省略结束标签的标签,并且与上一次解析到的开始标签相同,如下:

<p>max
<p>kaixin

p 标签是可以省略结束标签的标签,所以当解析到一个p标签的开始标签并且下一次遇到的标签也是p标签的开始标签时,会立即关闭第二个p标签。即调用:parseEndTag(tagName) 函数,然后由于第一个p标签缺少闭合标签所以会Vue会给你一个警告。

handleStartTag函数后续

接下来我们继续讲解handleStartTag函数后续的内容。

if (tagName) {
    lowerCasedTagName = tagName.toLowerCase();
    for (pos = stack.length - 1; pos &gt;= 0; pos--) {
        if (stack[pos].lowerCasedTag === lowerCasedTagName) {
            break
        }
    }
} else {
    // If no tag name is provided, clean shop
    pos = 0;
}

如果tagName存在,lowerCasedTagName 获取的是 tagName 小写之后的值,接下来开启一个 for 循环从后向前遍历 stack 栈,直到找到相应的位置,并且该位置索引会保存到 pos 变量中,如果 tagName 不存在,则直接将 pos 设置为 0 。

开头我们讲到 pos 变量会被用来判断是否有元素缺少闭合标签。怎么做到的呢?看完下面的代码你就明白了。

if (pos >= 0) {
    // Close all the open elements, up the stack
    for (var i = stack.length - 1; i >= pos; i--) {
        if (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);
    }
}

上面代码由三部分组成,即if...else if...else if。首先我们查看 if 语句块,当 pos >= 0 的时候就会走 if 语句块。在 if 语句块内开启一个 for 循环,同样是从后向前遍历 stack 数组,如果发现 stack 数组中存在索引大于 pos 的元素,那么该元素一定是缺少闭合标签的,这个时候如果是在非生产环境那么 Vue 便会打印一句警告,告诉你缺少闭合标签。除了打印一句警告之外,随后会调用 options.end(stack[i].tag, start, end) 立即将其闭合,这是为了保证解析结果的正确性。

您的支持是我们创作的动力!
温馨提示:本文作者系 ,经Web前端之家编辑修改或补充,转载请注明出处和本文链接:
https://jiangweishan.com/article/vuejs20220714a1.html

网友评论文明上网理性发言 已有0人参与

发表评论: