摘要:当字符串开头是时,可以匹配匹配尾标签。从结尾,找到所在位置批量闭合。
写文章不容易,点个赞呗兄弟
专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧
研究基于 Vue版本 【2.5.17】
如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧
【Vue原理】Compile - 源码版 之 标签解析
咳咳,上一篇文章,我们已经大致把 parse 的流程给记录了一遍,如果没看过,比较建议,先把这个流程给看了
Compile - 源码版 之 Parse 主要流程
但是忽略了其中的处理细节,比如标签怎么解析的,属性怎么解析的,而且这两个内容也是非常多的,所以需要多带带拎出来详细记录,不然混在一起,又臭又长
白话版在这~ Compile - 白话版
今天的内容是,记录 标签解析的 源码
首先,开篇之前呢,我们来了解一下文章会出现过的正则
相关正则var ncname = "[a-zA-Z_][w-.]*";
var qnameCapture = "((?:" + ncname + ":)?" + ncname + ")";
var startTagOpen = new RegExp(("^<" + qnameCapture));
var startTagClose = /^s*(/?)>/;
var endTag = new RegExp(("^" + qnameCapture + "[^>]*>"));
var attribute = /^s*([^s""<>/=]+)(?:s*(=)s*(?:"([^"]*)"+|"([^"]*)"+|([^s""=<>`]+)))?/;
主要是四个
startTagOpen匹配 头标签的 前半部分。当字符串开头是 头标签时,可以匹配
startTagClose匹配 头标签的 右尖括号。当字符串开头是 > 时,可以匹配
endTag匹配 尾标签。当字符串开头是 尾标签时 可以匹配
attribute匹配标签上的属性。当字符串开头是属性则可以匹配
好的,看完上面四个正则, 心里有个 * 数之后,相信下面的内容你会更加清晰些
下面的内容分为
1、循环遍历 template
2、处理 头标签
3、处理 尾标签
那么我们按一个个来说
循环遍历template通过上一篇内容,已经记录过 是怎么循环遍历 template 的了,就是通过 parseHTML 这个方法
这个方法,因为内容需要,也记录一遍
首先,什么是循环遍历template?
template 是一个字符串,所以每匹配完一个信息(比如头标签等),就会把template 截断到匹配的结束位置
比如 template 是
"1111"
当我们匹配完了 头标签,那么 template 就会被截断成
"1111
然后就这样一直循环匹配新的 template,直到 template 被截断成 空字符串,那么匹配完毕,其中跟截断有关的一个重要函数就是 advance
这个函数在下面的源码中用得非常多,需要牢记
其作用就是
1、截断template
2、保存当前截断的位置。比如你匹配了template到 字符串长度为5 的位置,那么 index 就是 4(从0开始)
function advance(n) {
index += n;
html = html.substring(n);
}
记住这个函数哦,我传入一个数字 n,就是要把 template 从 n 截取到结尾
然后下面就看看简化的 parseHTML 源码(如果嫌长,先跳到分析)
function parseHTML(html, options) {
// 保存所有标签的对象信息,tagName,attr,这样,在解析尾部标签的时候得到所属的层级关系以及父标签
var stack = [];
var index = 0;
var last;
while (html) {
last = html;
var textEnd = html.indexOf("<");
// 如果开头是 标签的 <
if (textEnd === 0) {
/**
* 如果开头的 < 属性尾标签
* 比如 html = "这段代码已经简化得很简单了,算是整体对 template 处理的一种把控我觉得
先匹配 < 的位置
1 如果 < 在template开头那么就是标签(这里先不讨论 字符串中的 <)
然后需要多一层判断
如果是尾标签的 <,那么交给 parseEndTag 处理
如果是头标签的 <,那么使用 handleStartTag 处理
2 如果 < 不在 template 开头那么表明 开头到 < 的这段位置是字符串,但是本文内容是标签解析,所以忽略这部分
然后每完成一次匹配,就需要调用 advace 去截断 template
然后现在,我们假定有下面这段处理
template = "111" parseHTML(template)
匹配 < 在开头,正则判断之后,发现不是 尾标签的 <,那么需要判断是不是 头标签的
然后使用 parseStartTag 方法去匹配头标签信息
匹配成功,使用 handleStartTag 方法处理
看到在 parseHTML 末尾声明了三个函数,为了避免太长,我挑了出来放在相应的内容讲
而之所以会在里面声明这个三个函数,是为了在这三个函数中,能访问到 parseHTML 中的变量,比如 stack,index
处理头标签 parseStartTag这个方法的作用就是
1、把头标签的所有信息集合起来,包括属性,标签名等
2、匹配完成之后同样调用 advance 去截断 template
3、把标签信息 返回
源码已经简化,并且有做流程注释,大家肯定看得懂,太烦的可以看后面的结果
function parseStartTag() {
// html ="111"
// start = ["111"
advance(start[0].length);
var end, attr;
// 循环匹配 属性 内容,保存属性列表
// 直到 template 开头是 头标签的 >
while (
// 匹配不到头标签的 >,开始匹配 属性内容
// end = null
! (end = html.match(startTagClose))
&&
// 开始匹配 属性内容
// attr = ["name=1", "name", "=" ]
(attr = html.match(attribute))
) {
advance(attr[0].length);
match.attrs.push(attr);
}
// 匹配到 起始标签的 >,标签属性那些已经匹配完毕了
// 返回收集到的 标签信息
if (end) {
advance(end[0].length);
// 如果是单标签,那么 unarySlash 的值是 /,比如
match.unarySlash = end[1];
match.end = index;
return match
}
}
}
我们来记录下这个方法会返回什么
比如
html = ""
parseStartTag 处理之后会返回以下内容
{
tagName: "div",
attrs: [
[" name=1", "name", "=" ,
undefined, undefined, "1"]
],
unarySlash: "",
start: 0,
end: 12
}
其中 的属性
start:头标签的 < 在 template 中的位置
end:头标签的 > 在 tempalte 中的位置
attrs:是一个二维数组,存放着所有头标签的 属性信息
unarySlash:表示这个标签是否是 单标签。如果是 true,那么不是单标签。如果是 false,那么就是单标签。一切在于匹配头标签时,有没有匹配到 /
通过 parseHTML 我们看到,parseStartTag 返回的 头标签信息,给了谁呢?
没错,传给了 handleStartTag
handleStartTag这个函数的作用
1、接收上一步收集的标签信息
2、处理属性,转换一下其格式
3、保存进 stack,记录 DOM 父子结构顺序
function handleStartTag(match) {
var tagName = match.tagName;
var unarySlash = match.unarySlash;
// 判断是不是单标签,input,img 这些
var unary = isUnaryTag$$1(tagName) || !!unarySlash;
var l = match.attrs.length;
var attrs = new Array(l);
// 把属性数组转换成对象
for (var i = 0; i < l; i++) {
var args = match.attrs[i];
// args = [" name=1", "name", "=",
undefined, undefined, "1" ]
var value = args[3] || args[4] || args[5] || "";
attrs[i] = {
name: args[1],
value: value
};
}
// 不是单标签,才存到 stack
if (!unary) {
stack.push({
tag: tagName,
attrs: attrs
});
}
if (options.start) {
options.start(
tagName, attrs, unary,
match.start, match.end
);
}
}
最后,把该标签得到的信息,传给 options.start,帮助建立 template 的 ast(在 上篇文章 Compile - 源码版 之 Parse 主要流程 中有说明=)
那么到这里,头标签 匹配完了
然后 template 被截断成
"111"
文本处理的部分我们跳过,跳到尾标签,所以 template 为
""
然后匹配到尾标签,交给 parseEndTag 处理
那么进入我们的下一小节内容,处理尾标签
处理尾标签在 parseHTML 中看到
当使用 endTag 这个正则成功匹配到尾标签时,会调用 parseEndTag
而 这个函数呢,可能没有那么好理解了,你可以先跳过源码,翻到后面的解析
function parseEndTag(tagName, start, end) {
var pos, lowerCasedTagName;
// 从stack 最后查找匹配的 tagName 位置
if (tagName) {
// 如果在 stack 中找不到,pos 最后是 -1
for (pos = stack.length - 1; pos >= 0; pos--) {
if (stack[pos].tagName===tagName) break
}
}
else {
// 如果没有提供标签名,那么关闭所有存在 stack 中的 起始标签
pos = 0
}
// 批量 stack pos 位置后的所有标签
if (pos >= 0) {
// 关闭 pos 位置之后所有的起始标签,避免有些标签没有尾标签
// 比如 stack.len = 7 , pos=5 ,那么就关闭 最后两个
for (var i = stack.length - 1; i >= pos; i--) {
if (options.end) {
options.end(stack[i].tag, start, end);
}
}
// 匹配完闭合标签之后,就把 匹配了的标签头 给 移除了
stack.length = pos;
}
}
函数功能分为两部分
1、找位置。从 stack 结尾,找到 tagName 所在位置 pos
2、批量闭合。闭合并移除 stack 在 pos 位置后的所有 tag
现在我们先给一个模板,然后慢慢解释
现在已经连续匹配到三个 头标签,div,header,span
此时 stack= [ div, header, span ]
然后开始匹配到 ,然后去 stack 末尾找 span
确定 span 在 stack 的位置 pos 后,批量闭合stack 的 pos 后的所有标签
为什么从末尾开始?因为 stack 是按 template 的标签顺序存放的,肯定是先匹配到父标签,再匹配到子标签
碰到 尾标签,肯定找最近匹配到的头标签,那么肯定是刚存入 stack 的,那么就是在 stack 的结尾
为什么闭合 pos位置后所有标签?因为怕有刁民不写闭合标签,比如模板是这样
同样,匹配完三个头标签
stack = [ div, header, span ]
接着匹配到 ,于是在 stack 末尾中找 header
在倒数第二个,那么 pos = 1
根据 stack 的长度, 遍历一次 stack,闭合到末尾,就是闭合 stack[1], stack[2]
就是闭合 header 和 span 了
就是为了避免有屁民没写 尾标签
你说单标签没有尾标签啊?是啊,但是单标签 不存进 stack 啊哈哈哈
在 handleStartTag 中有处理哦
接下来你就可以去看 parseEndTag 的源码了,肯定能看懂
怎么闭合的呢?parseEndTag 就做了匹配 tag 位置 和容错处理
主要实现闭合功能在调用 options.end 中,通过不断地传入尾标签从而完成闭合功能
如果你看过上篇文章 Compile - 源码版 之 Parse 主要流程 就知道,闭合是为了形成正确的节点关系树
也可以说,是为了明确节点的父子关系
总结通过这次我们知道了
1、标签的匹配方法
2、怎么利用头标签收集信息
3、闭合标签的处理方法
最后鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,有重谢
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/106588.html
写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,请点击 下面链接 或者 拉到 下面关注公众号也可以吧 【Vue原理】Compile - 源码版 之 Parse 主要流程 本文难度较繁琐,需要耐心观看,如果你对 compile 源码暂时...
摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理源码版之属性解析哈哈哈,今天终 写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究...
摘要:一旦我们检测到这些子树,我们可以把它们变成常数,这样我们就不需要了在每次重新渲染时为它们创建新的节点在修补过程中完全跳过它们。否则,吊装费用将会增加好处大于好处,最好总是保持新鲜。 写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版难看,...
摘要:写文章不容易,点个赞呗兄弟专注源码分享,文章分为白话版和源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于版本如果你觉得排版难看,请点击下面链接或者拉到下面关注公众号也可以吧原理源码版之节点数据拼接上一篇我们 写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究...
摘要:还原的难度就在于变成模板了,因为其他的什么等是原封不动的哈哈,可是直接照抄最后鉴于本人能力有限,难免会有疏漏错误的地方,请大家多多包涵,如果有任何描述不当的地方,欢迎后台联系本人,有重谢 写文章不容易,点个赞呗兄弟 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧研究基于 Vue版本 【2.5.17】 如果你觉得排版...
阅读 4869·2021-09-24 10:24
阅读 1741·2021-09-22 16:01
阅读 2979·2021-09-06 15:02
阅读 1316·2019-08-30 13:01
阅读 1167·2019-08-30 10:52
阅读 813·2019-08-29 16:36
阅读 2448·2019-08-29 12:51
阅读 2531·2019-08-28 18:29